diff --git a/.gitignore b/.gitignore index da8569baa3..ae3b49af21 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,5 @@ pom.xml.versionsBackup **/.env .instance_id rebuild-docker.sh +.run/** +.run \ No newline at end of file diff --git a/application/pom.xml b/application/pom.xml index c2ae7eccc2..8a12fe05bb 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT thingsboard application @@ -145,10 +145,6 @@ org.springframework.boot spring-boot-starter-websocket - - org.springframework.cloud - spring-cloud-starter-oauth2 - org.springframework.security spring-security-oauth2-client @@ -197,6 +193,14 @@ com.sun.mail javax.mail + + com.twilio.sdk + twilio + + + com.amazonaws + aws-java-sdk-sns + org.apache.curator curator-recipes diff --git a/application/src/main/data/json/demo/dashboards/gateways.json b/application/src/main/data/json/demo/dashboards/gateways.json index 34474ea890..0a18b10a52 100644 --- a/application/src/main/data/json/demo/dashboards/gateways.json +++ b/application/src/main/data/json/demo/dashboards/gateways.json @@ -955,7 +955,7 @@ }, "methodName": "gateway_restart", "methodParams": "{}", - "buttonText": "gateway restart" + "buttonText": "GATEWAY RESTART" }, "title": "New RPC Button", "dropShadow": true, diff --git a/application/src/main/data/json/demo/dashboards/raspberry_pi_gpio_demo_dashboard.json b/application/src/main/data/json/demo/dashboards/raspberry_pi_gpio_demo_dashboard.json deleted file mode 100644 index 1dfba7c8d4..0000000000 --- a/application/src/main/data/json/demo/dashboards/raspberry_pi_gpio_demo_dashboard.json +++ /dev/null @@ -1,700 +0,0 @@ -{ - "title": "Raspberry PI GPIO Demo Dashboard", - "configuration": { - "description": "Demo dashboard for Raspberry PI GPIO Demo", - "widgets": { - "602177f6-267b-cb87-4e8f-e23d7fb2f61c": { - "isSystemType": true, - "bundleAlias": "gpio_widgets", - "typeAlias": "raspberry_pi_gpio_control", - "type": "rpc", - "title": "New widget", - "sizeX": 6, - "sizeY": 10, - "config": { - "targetDeviceAliases": [], - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "0px", - "settings": { - "parseGpioStatusFunction": "return body[pin] === true;", - "gpioStatusChangeRequest": { - "method": "setGpioStatus", - "paramsBody": "{\n \"pin\": \"{$pin}\",\n \"enabled\": \"{$enabled}\"\n}" - }, - "requestTimeout": 500, - "switchPanelBackgroundColor": "#008a00", - "gpioStatusRequest": { - "method": "getGpioStatus", - "paramsBody": "{}" - }, - "gpioList": [ - { - "pin": 7, - "label": "GPIO 4 (GPCLK0)", - "row": 3, - "col": 0, - "_uniqueKey": 0 - }, - { - "pin": 11, - "label": "GPIO 17", - "row": 5, - "col": 0, - "_uniqueKey": 1 - }, - { - "pin": 12, - "label": "GPIO 18", - "row": 5, - "col": 1, - "_uniqueKey": 2 - }, - { - "_uniqueKey": 3, - "pin": 13, - "label": "GPIO 27", - "row": 6, - "col": 0 - }, - { - "_uniqueKey": 4, - "pin": 15, - "label": "GPIO 22", - "row": 7, - "col": 0 - }, - { - "_uniqueKey": 5, - "pin": 16, - "label": "GPIO 23", - "row": 7, - "col": 1 - }, - { - "_uniqueKey": 6, - "pin": 18, - "label": "GPIO 24", - "row": 8, - "col": 1 - }, - { - "_uniqueKey": 7, - "pin": 22, - "label": "GPIO 25", - "row": 10, - "col": 1 - }, - { - "_uniqueKey": 8, - "pin": 29, - "label": "GPIO 5", - "row": 14, - "col": 0 - }, - { - "_uniqueKey": 9, - "pin": 31, - "label": "GPIO 6", - "row": 15, - "col": 0 - }, - { - "_uniqueKey": 10, - "pin": 32, - "label": "GPIO 12", - "row": 15, - "col": 1 - }, - { - "_uniqueKey": 11, - "pin": 33, - "label": "GPIO 13", - "row": 16, - "col": 0 - }, - { - "_uniqueKey": 12, - "pin": 35, - "label": "GPIO 19", - "row": 17, - "col": 0 - }, - { - "_uniqueKey": 13, - "pin": 36, - "label": "GPIO 16", - "row": 17, - "col": 1 - }, - { - "_uniqueKey": 14, - "pin": 37, - "label": "GPIO 26", - "row": 18, - "col": 0 - }, - { - "_uniqueKey": 15, - "pin": 38, - "label": "GPIO 20", - "row": 18, - "col": 1 - }, - { - "_uniqueKey": 16, - "pin": 40, - "label": "GPIO 21", - "row": 19, - "col": 1 - } - ] - }, - "title": "Raspberry Pi GPIO Control Panel", - "datasources": [], - "targetDeviceAliasIds": [ - "f26b12b6-6938-e1a0-85ec-d88a1f23e382" - ] - }, - "row": 0, - "col": 0, - "id": "602177f6-267b-cb87-4e8f-e23d7fb2f61c" - }, - "3cca52a5-e874-eb43-b444-8efa01e663c8": { - "isSystemType": true, - "bundleAlias": "gpio_widgets", - "typeAlias": "raspberry_pi_gpio_panel", - "type": "latest", - "title": "New widget", - "sizeX": 7, - "sizeY": 10, - "config": { - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "0px", - "settings": { - "gpioList": [ - { - "pin": 1, - "label": "3.3V", - "row": 0, - "col": 0, - "color": "#fc9700", - "_uniqueKey": 0 - }, - { - "pin": 2, - "label": "5V", - "row": 0, - "col": 1, - "color": "#fb0000", - "_uniqueKey": 1 - }, - { - "pin": 3, - "label": "GPIO 2 (I2C1_SDA)", - "row": 1, - "col": 0, - "color": "#02fefb", - "_uniqueKey": 2 - }, - { - "color": "#fb0000", - "pin": 4, - "label": "5V", - "row": 1, - "col": 1 - }, - { - "color": "#02fefb", - "pin": 5, - "label": "GPIO 3 (I2C1_SCL)", - "row": 2, - "col": 0 - }, - { - "color": "#000000", - "pin": 6, - "label": "GND", - "row": 2, - "col": 1 - }, - { - "color": "#00fd00", - "pin": 7, - "label": "GPIO 4 (GPCLK0)", - "row": 3, - "col": 0 - }, - { - "color": "#fdfb00", - "pin": 8, - "label": "GPIO 14 (UART_TXD)", - "row": 3, - "col": 1 - }, - { - "color": "#000000", - "pin": 9, - "label": "GND", - "row": 4, - "col": 0 - }, - { - "color": "#fdfb00", - "pin": 10, - "label": "GPIO 15 (UART_RXD)", - "row": 4, - "col": 1 - }, - { - "color": "#00fd00", - "pin": 11, - "label": "GPIO 17", - "row": 5, - "col": 0 - }, - { - "color": "#00fd00", - "pin": 12, - "label": "GPIO 18", - "row": 5, - "col": 1 - }, - { - "color": "#00fd00", - "pin": 13, - "label": "GPIO 27", - "row": 6, - "col": 0 - }, - { - "color": "#000000", - "pin": 14, - "label": "GND", - "row": 6, - "col": 1 - }, - { - "color": "#00fd00", - "pin": 15, - "label": "GPIO 22", - "row": 7, - "col": 0 - }, - { - "color": "#00fd00", - "pin": 16, - "label": "GPIO 23", - "row": 7, - "col": 1 - }, - { - "color": "#fc9700", - "pin": 17, - "label": "3.3V", - "row": 8, - "col": 0 - }, - { - "color": "#00fd00", - "pin": 18, - "label": "GPIO 24", - "row": 8, - "col": 1 - }, - { - "color": "#fd01fd", - "pin": 19, - "label": "GPIO 10 (SPI_MOSI)", - "row": 9, - "col": 0 - }, - { - "color": "#000000", - "pin": 20, - "label": "GND", - "row": 9, - "col": 1 - }, - { - "color": "#fd01fd", - "pin": 21, - "label": "GPIO 9 (SPI_MISO)", - "row": 10, - "col": 0 - }, - { - "color": "#00fd00", - "pin": 22, - "label": "GPIO 25", - "row": 10, - "col": 1 - }, - { - "color": "#fd01fd", - "pin": 23, - "label": "GPIO 11 (SPI_SCLK)", - "row": 11, - "col": 0 - }, - { - "color": "#fd01fd", - "pin": 24, - "label": "GPIO 8 (SPI_CE0)", - "row": 11, - "col": 1 - }, - { - "color": "#000000", - "pin": 25, - "label": "GND", - "row": 12, - "col": 0 - }, - { - "color": "#fd01fd", - "pin": 26, - "label": "GPIO 7 (SPI_CE1)", - "row": 12, - "col": 1 - }, - { - "color": "#ffffff", - "pin": 27, - "label": "ID_SD", - "row": 13, - "col": 0 - }, - { - "color": "#ffffff", - "pin": 28, - "label": "ID_SC", - "row": 13, - "col": 1 - }, - { - "color": "#00fd00", - "pin": 29, - "label": "GPIO 5", - "row": 14, - "col": 0 - }, - { - "color": "#000000", - "pin": 30, - "label": "GND", - "row": 14, - "col": 1 - }, - { - "color": "#00fd00", - "pin": 31, - "label": "GPIO 6", - "row": 15, - "col": 0 - }, - { - "color": "#00fd00", - "pin": 32, - "label": "GPIO 12", - "row": 15, - "col": 1 - }, - { - "color": "#00fd00", - "pin": 33, - "label": "GPIO 13", - "row": 16, - "col": 0 - }, - { - "color": "#000000", - "pin": 34, - "label": "GND", - "row": 16, - "col": 1 - }, - { - "color": "#00fd00", - "pin": 35, - "label": "GPIO 19", - "row": 17, - "col": 0 - }, - { - "color": "#00fd00", - "pin": 36, - "label": "GPIO 16", - "row": 17, - "col": 1 - }, - { - "color": "#00fd00", - "pin": 37, - "label": "GPIO 26", - "row": 18, - "col": 0 - }, - { - "color": "#00fd00", - "pin": 38, - "label": "GPIO 20", - "row": 18, - "col": 1 - }, - { - "color": "#000000", - "pin": 39, - "label": "GND", - "row": 19, - "col": 0 - }, - { - "color": "#00fd00", - "pin": 40, - "label": "GPIO 21", - "row": 19, - "col": 1 - } - ], - "ledPanelBackgroundColor": "#008a00" - }, - "title": "Raspberry Pi GPIO Status Panel", - "datasources": [ - { - "type": "entity", - "dataKeys": [ - { - "name": "7", - "type": "attribute", - "label": "7", - "color": "#2196f3", - "settings": {}, - "_hash": 0.20925966435886978 - }, - { - "name": "11", - "type": "attribute", - "label": "11", - "color": "#4caf50", - "settings": {}, - "_hash": 0.330267349594344 - }, - { - "name": "12", - "type": "attribute", - "label": "12", - "color": "#f44336", - "settings": {}, - "_hash": 0.5040578704481748 - }, - { - "name": "13", - "type": "attribute", - "label": "13", - "color": "#ffc107", - "settings": {}, - "_hash": 0.588956328191639 - }, - { - "name": "15", - "type": "attribute", - "label": "15", - "color": "#607d8b", - "settings": {}, - "_hash": 0.9229040530336119 - }, - { - "name": "16", - "type": "attribute", - "label": "16", - "color": "#9c27b0", - "settings": {}, - "_hash": 0.8692315253041654 - }, - { - "name": "18", - "type": "attribute", - "label": "18", - "color": "#8bc34a", - "settings": {}, - "_hash": 0.41465562857521543 - }, - { - "name": "22", - "type": "attribute", - "label": "22", - "color": "#3f51b5", - "settings": {}, - "_hash": 0.36135260043112827 - }, - { - "name": "29", - "type": "attribute", - "label": "29", - "color": "#e91e63", - "settings": {}, - "_hash": 0.9904592276182183 - }, - { - "name": "31", - "type": "attribute", - "label": "31", - "color": "#ffeb3b", - "settings": {}, - "_hash": 0.038330985429919195 - }, - { - "name": "32", - "type": "attribute", - "label": "32", - "color": "#03a9f4", - "settings": {}, - "_hash": 0.4334683890135089 - }, - { - "name": "33", - "type": "attribute", - "label": "33", - "color": "#ff9800", - "settings": {}, - "_hash": 0.6487255992492305 - }, - { - "name": "35", - "type": "attribute", - "label": "35", - "color": "#673ab7", - "settings": {}, - "_hash": 0.971555321150732 - }, - { - "name": "36", - "type": "attribute", - "label": "36", - "color": "#cddc39", - "settings": {}, - "_hash": 0.7826129728424382 - }, - { - "name": "37", - "type": "attribute", - "label": "37", - "color": "#009688", - "settings": {}, - "_hash": 0.44925676517537627 - }, - { - "name": "38", - "type": "attribute", - "label": "38", - "color": "#795548", - "settings": {}, - "_hash": 0.051518155759787465 - }, - { - "name": "40", - "type": "attribute", - "label": "40", - "color": "#00bcd4", - "settings": {}, - "_hash": 0.8733296686871144 - } - ], - "name": "RPi", - "entityAliasId": "f26b12b6-6938-e1a0-85ec-d88a1f23e382" - } - ], - "timewindow": { - "realtime": { - "timewindowMs": 60000 - } - } - }, - "row": 0, - "col": 6, - "id": "3cca52a5-e874-eb43-b444-8efa01e663c8" - } - }, - "states": { - "default": { - "name": "Default", - "root": true, - "layouts": { - "main": { - "widgets": { - "602177f6-267b-cb87-4e8f-e23d7fb2f61c": { - "sizeX": 6, - "sizeY": 10, - "row": 0, - "col": 0 - }, - "3cca52a5-e874-eb43-b444-8efa01e663c8": { - "sizeX": 7, - "sizeY": 10, - "row": 0, - "col": 6 - } - }, - "gridSettings": { - "backgroundColor": "#eeeeee", - "color": "rgba(0,0,0,0.870588)", - "columns": 24, - "margins": [ - 10, - 10 - ], - "backgroundSizeMode": "100%" - } - } - } - } - }, - "entityAliases": { - "f26b12b6-6938-e1a0-85ec-d88a1f23e382": { - "id": "f26b12b6-6938-e1a0-85ec-d88a1f23e382", - "alias": "RPi", - "filter": { - "type": "entityName", - "resolveMultiple": false, - "entityType": "DEVICE", - "entityNameFilter": "Raspberry Pi Demo Device" - } - } - }, - "timewindow": { - "displayValue": "", - "selectedTab": 0, - "realtime": { - "interval": 1000, - "timewindowMs": 60000 - }, - "history": { - "historyType": 0, - "interval": 1000, - "timewindowMs": 60000, - "fixedTimewindow": { - "startTimeMs": 1498653734150, - "endTimeMs": 1498740134150 - } - }, - "aggregation": { - "type": "AVG", - "limit": 200 - } - }, - "settings": { - "stateControllerId": "default", - "showTitle": true, - "showDashboardsSelect": true, - "showEntitiesSelect": true, - "showDashboardTimewindow": true, - "showDashboardExport": true, - "toolbarAlwaysOpen": false - } - }, - "name": "Raspberry PI GPIO Demo Dashboard" -} \ No newline at end of file diff --git a/application/src/main/data/json/demo/dashboards/temperature___humidity_demo_dashboard.json b/application/src/main/data/json/demo/dashboards/temperature___humidity_demo_dashboard.json deleted file mode 100644 index 4d4f901558..0000000000 --- a/application/src/main/data/json/demo/dashboards/temperature___humidity_demo_dashboard.json +++ /dev/null @@ -1,264 +0,0 @@ -{ - "title": "Temperature & Humidity Demo Dashboard", - "configuration": { - "description": "Demo dashboard for sample applications that upload temperature and humidity received from DHT11 or DHT22 sensors", - "widgets": { - "03e06986-1c50-e9e4-267c-2bae930ad9a2": { - "isSystemType": true, - "bundleAlias": "digital_gauges", - "typeAlias": "digital_thermometer", - "type": "latest", - "title": "New widget", - "sizeX": 5, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "dataKeys": [ - { - "name": "temperature", - "type": "timeseries", - "label": "temperature", - "color": "#2196f3", - "settings": {}, - "_hash": 0.3720839051412099 - } - ], - "name": "DHT11", - "entityAliasId": "63a93238-c13f-4403-4bcc-9ccc86bd6a62" - } - ], - "timewindow": { - "realtime": { - "timewindowMs": 60000 - } - }, - "showTitle": false, - "backgroundColor": "#000000", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "0px", - "settings": { - "maxValue": 50, - "donutStartAngle": 90, - "showValue": true, - "showMinMax": true, - "gaugeWidthScale": 1, - "levelColors": [ - "#304ffe", - "#7e57c2", - "#ff4081", - "#d32f2f" - ], - "refreshAnimationType": "<>", - "refreshAnimationTime": 700, - "startAnimationType": "<>", - "startAnimationTime": 700, - "titleFont": { - "family": "RobotoDraft", - "size": 12, - "style": "normal", - "weight": "500" - }, - "labelFont": { - "family": "RobotoDraft", - "size": 8, - "style": "normal", - "weight": "500" - }, - "valueFont": { - "family": "Segment7Standard", - "style": "normal", - "weight": "500", - "size": 18 - }, - "minMaxFont": { - "family": "Segment7Standard", - "size": 12, - "style": "normal", - "weight": "500" - }, - "dashThickness": 1.5, - "decimals": 0, - "minValue": 0, - "units": "°C", - "gaugeColor": "#333333", - "neonGlowBrightness": 35, - "gaugeType": "donut", - "showTitle": false - }, - "title": "Temperature" - }, - "row": 0, - "col": 0, - "id": "03e06986-1c50-e9e4-267c-2bae930ad9a2" - }, - "88808eb1-d381-9970-c852-e3499df68bd8": { - "isSystemType": true, - "bundleAlias": "digital_gauges", - "typeAlias": "digital_vertical_bar", - "type": "latest", - "title": "New widget", - "sizeX": 3, - "sizeY": 5, - "config": { - "datasources": [ - { - "type": "entity", - "dataKeys": [ - { - "name": "humidity", - "type": "timeseries", - "label": "humidity", - "color": "#2196f3", - "settings": {}, - "_hash": 0.9492802776509441 - } - ], - "name": "DHT11", - "entityAliasId": "63a93238-c13f-4403-4bcc-9ccc86bd6a62" - } - ], - "timewindow": { - "realtime": { - "timewindowMs": 60000 - } - }, - "showTitle": false, - "backgroundColor": "#000000", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "0px", - "settings": { - "maxValue": 100, - "donutStartAngle": 90, - "showValue": true, - "showMinMax": true, - "gaugeWidthScale": 0.75, - "levelColors": [ - "#3d5afe", - "#f44336" - ], - "refreshAnimationType": "<>", - "refreshAnimationTime": 700, - "startAnimationType": "<>", - "startAnimationTime": 700, - "titleFont": { - "family": "RobotoDraft", - "size": 12, - "style": "normal", - "weight": "500" - }, - "labelFont": { - "family": "RobotoDraft", - "size": 8, - "style": "normal", - "weight": "500" - }, - "valueFont": { - "family": "Segment7Standard", - "style": "normal", - "weight": "500", - "size": 14 - }, - "minMaxFont": { - "family": "Segment7Standard", - "size": 8, - "style": "normal", - "weight": "normal", - "color": "#cccccc" - }, - "neonGlowBrightness": 20, - "decimals": 0, - "showUnitTitle": true, - "gaugeColor": "#171a1c", - "gaugeType": "verticalBar", - "showTitle": false, - "minValue": 0, - "dashThickness": 1.2 - }, - "title": "Humidity" - }, - "row": 0, - "col": 5, - "id": "88808eb1-d381-9970-c852-e3499df68bd8" - } - }, - "states": { - "default": { - "name": "Default", - "root": true, - "layouts": { - "main": { - "widgets": { - "03e06986-1c50-e9e4-267c-2bae930ad9a2": { - "sizeX": 5, - "sizeY": 5, - "row": 0, - "col": 0 - }, - "88808eb1-d381-9970-c852-e3499df68bd8": { - "sizeX": 3, - "sizeY": 5, - "row": 0, - "col": 5 - } - }, - "gridSettings": { - "backgroundColor": "#eeeeee", - "color": "rgba(0,0,0,0.870588)", - "columns": 24, - "margins": [ - 10, - 10 - ], - "backgroundSizeMode": "100%" - } - } - } - } - }, - "entityAliases": { - "63a93238-c13f-4403-4bcc-9ccc86bd6a62": { - "id": "63a93238-c13f-4403-4bcc-9ccc86bd6a62", - "alias": "DHT11", - "filter": { - "type": "entityName", - "resolveMultiple": false, - "entityType": "DEVICE", - "entityNameFilter": "DHT11 Demo Device" - } - } - }, - "timewindow": { - "displayValue": "", - "selectedTab": 0, - "realtime": { - "interval": 1000, - "timewindowMs": 60000 - }, - "history": { - "historyType": 0, - "interval": 1000, - "timewindowMs": 60000, - "fixedTimewindow": { - "startTimeMs": 1498653790019, - "endTimeMs": 1498740190019 - } - }, - "aggregation": { - "type": "AVG", - "limit": 200 - } - }, - "settings": { - "stateControllerId": "default", - "showTitle": true, - "showDashboardsSelect": true, - "showEntitiesSelect": true, - "showDashboardTimewindow": true, - "showDashboardExport": true, - "toolbarAlwaysOpen": false - } - }, - "name": "Temperature & Humidity Demo Dashboard" -} \ No newline at end of file diff --git a/application/src/main/data/json/demo/dashboards/thermostats.json b/application/src/main/data/json/demo/dashboards/thermostats.json index 06edbcbef0..4f486effdf 100644 --- a/application/src/main/data/json/demo/dashboards/thermostats.json +++ b/application/src/main/data/json/demo/dashboards/thermostats.json @@ -147,9 +147,9 @@ "name": "Add", "icon": "add", "type": "customPretty", - "customHtml": "
\n \n

Add thermostat

\n \n \n
\n \n \n
\n
\n \n Thermostat name\n \n \n Thermostat name is required.\n \n \n
\n \n High temperature alarm\n \n \n High temperature threshold, °C\n \n \n High temperature threshold is required.\n \n \n \n \n Low humidity alarm\n \n \n \n Low humidity threshold, %\n \n \n Low humidity threshold is required.\n \n \n
\n
\n
\n \n \n
\n
", + "customHtml": "
\n \n

Add thermostat

\n \n \n
\n \n \n
\n
\n \n Thermostat name\n \n \n Thermostat name is required.\n \n \n
\n \n High temperature alarm\n \n \n High temperature threshold, °C\n \n \n High temperature threshold is required.\n \n \n \n \n Low humidity alarm\n \n \n \n Low humidity threshold, %\n \n \n Low humidity threshold is required.\n \n \n
\n
\n
\n \n \n
\n
", "customCss": ".add-entity-form{\n width: 300px;\n}\n", - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\n\nopenAddEntityDialog();\n\nfunction openAddEntityDialog() {\n customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe();\n}\n\nfunction AddEntityDialogController(instance) {\n let vm = instance;\n \n vm.addEntityFormGroup = vm.fb.group({\n entityName: ['', [vm.validators.required]],\n attributes: vm.fb.group({\n alarmTemperature: [false],\n thresholdTemperature: [{value: null, disabled: true}],\n alarmHumidity: [false],\n thresholdHumidity: [{value: null, disabled: true}]\n })\n });\n \n vm.addEntityFormGroup.get('attributes').get('alarmTemperature').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.addEntityFormGroup.get('attributes').get('thresholdTemperature').enable();\n } else {\n vm.addEntityFormGroup.get('attributes').get('thresholdTemperature').disable();\n }\n });\n \n vm.addEntityFormGroup.get('attributes').get('alarmHumidity').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.addEntityFormGroup.get('attributes').get('thresholdHumidity').enable();\n } else {\n vm.addEntityFormGroup.get('attributes').get('thresholdHumidity').disable();\n }\n });\n\n vm.save = function() {\n vm.addEntityFormGroup.markAsPristine();\n saveEntityObservable().subscribe(\n function (entity) {\n saveAttributes(entity.id).subscribe(\n function () {\n widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n }\n );\n };\n \n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n \n function saveEntityObservable() {\n const formValues = vm.addEntityFormGroup.value;\n let entity = {\n name: formValues.entityName,\n type: \"thermostat\"\n };\n return deviceService.saveDevice(entity);\n }\n \n function saveAttributes(entityId) {\n let attributes = vm.addEntityFormGroup.get('attributes').value;\n let attributesArray = [];\n for (let key in attributes) {\n if(attributes[key] !== null) {\n attributesArray.push({key: key, value: attributes[key]});\n }\n }\n if (attributesArray.length > 0) {\n return attributeService.saveEntityAttributes(entityId, \"SERVER_SCOPE\", attributesArray);\n } else {\n return widgetContext.rxjs.of([]);\n }\n }\n}", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\n\nopenAddEntityDialog();\n\nfunction openAddEntityDialog() {\n customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe();\n}\n\nfunction AddEntityDialogController(instance) {\n let vm = instance;\n \n vm.addEntityFormGroup = vm.fb.group({\n entityName: ['', [vm.validators.required]],\n attributes: vm.fb.group({\n temperatureAlarmFlag: [false],\n temperatureAlarmThreshold: [{value: null, disabled: true}],\n humidityAlarmFlag: [false],\n humidityAlarmThreshold: [{value: null, disabled: true}]\n })\n });\n \n vm.addEntityFormGroup.get('attributes').get('temperatureAlarmFlag').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.addEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').enable();\n } else {\n vm.addEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').disable();\n }\n });\n \n vm.addEntityFormGroup.get('attributes').get('humidityAlarmFlag').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.addEntityFormGroup.get('attributes').get('humidityAlarmThreshold').enable();\n } else {\n vm.addEntityFormGroup.get('attributes').get('humidityAlarmThreshold').disable();\n }\n });\n\n vm.save = function() {\n vm.addEntityFormGroup.markAsPristine();\n saveEntityObservable().subscribe(\n function (entity) {\n saveAttributes(entity.id).subscribe(\n function () {\n widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n }\n );\n };\n \n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n \n function saveEntityObservable() {\n const formValues = vm.addEntityFormGroup.value;\n let entity = {\n name: formValues.entityName,\n type: \"thermostat\"\n };\n return deviceService.saveDevice(entity);\n }\n \n function saveAttributes(entityId) {\n let attributes = vm.addEntityFormGroup.get('attributes').value;\n let attributesArray = [];\n for (let key in attributes) {\n if(attributes[key] !== null) {\n attributesArray.push({key: key, value: attributes[key]});\n }\n }\n if (attributesArray.length > 0) {\n return attributeService.saveEntityAttributes(entityId, \"SERVER_SCOPE\", attributesArray);\n } else {\n return widgetContext.rxjs.of([]);\n }\n }\n}", "customResources": [], "id": "8ab5a518-67d2-b6a2-956d-81fd512294b2" } @@ -167,9 +167,9 @@ "name": "Edit", "icon": "edit", "type": "customPretty", - "customHtml": "
\n \n

Edit thermostat {{entityName}}

\n \n \n
\n \n \n
\n
\n \n Thermostat name\n \n \n
\n \n High temperature alarm\n \n \n High temperature threshold, °C\n \n \n High temperature threshold is required.\n \n \n\n \n Low humidity alarm\n \n\n \n Low humidity threshold, %\n \n \n Low humidity threshold is required.\n \n \n
\n
\n
\n \n \n
\n
", + "customHtml": "
\n \n

Edit thermostat {{entityName}}

\n \n \n
\n \n \n
\n
\n \n Thermostat name\n \n \n
\n \n High temperature alarm\n \n \n High temperature threshold, °C\n \n \n High temperature threshold is required.\n \n \n\n \n Low humidity alarm\n \n\n \n Low humidity threshold, %\n \n \n Low humidity threshold is required.\n \n \n
\n
\n
\n \n \n
\n
", "customCss": ".edit-entity-form{\n width: 300px;\n}", - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n \n vm.entityId = entityId;\n vm.entityName = entityName;\n vm.attributes = {};\n \n vm.editEntityFormGroup = vm.fb.group({\n entityName: [''],\n attributes: vm.fb.group({\n alarmTemperature: [false],\n thresholdTemperature: [{value: null, disabled: true}],\n alarmHumidity: [false],\n thresholdHumidity: [{value: null, disabled: true}]\n })\n });\n \n vm.editEntityFormGroup.get('attributes').get('alarmTemperature').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.editEntityFormGroup.get('attributes').get('thresholdTemperature').enable();\n } else {\n vm.editEntityFormGroup.get('attributes').get('thresholdTemperature').disable();\n }\n });\n \n vm.editEntityFormGroup.get('attributes').get('alarmHumidity').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.editEntityFormGroup.get('attributes').get('thresholdHumidity').enable();\n } else {\n vm.editEntityFormGroup.get('attributes').get('thresholdHumidity').disable();\n }\n });\n \n \n getEntityInfo();\n \n \n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveAttributes(entityId).subscribe(\n function () {\n vm.dialogRef.close(null);\n }\n );\n };\n \n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n \n function getEntityAttributes(attributes) {\n for (var i = 0; i < attributes.length; i++) {\n vm.attributes[attributes[i].key] = attributes[i].value;\n }\n }\n \n function getEntityInfo() {\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE').subscribe(\n function (attributes) {\n getEntityAttributes(attributes);\n vm.editEntityFormGroup.patchValue({\n entityName: vm.entityName,\n attributes: vm.attributes\n });\n // if(vm.attributes.alarmTemperature) {\n // vm.editEntityFormGroup.get('attributes').get('thresholdTemperature').enable();\n // }\n // if(vm.attributes.alarmHumidity) {\n // vm.editEntityFormGroup.get('attributes').get('thresholdHumidity').enable();\n // }\n }\n );\n }\n \n function saveAttributes(entityId) {\n let attributes = vm.editEntityFormGroup.get('attributes').value;\n let attributesArray = [];\n for (let key in attributes) {\n if (attributes[key] !== vm.attributes[key]) {\n attributesArray.push({key: key, value: attributes[key]});\n }\n }\n if (attributesArray.length > 0) {\n return attributeService.saveEntityAttributes(entityId, \"SERVER_SCOPE\", attributesArray);\n } else {\n return widgetContext.rxjs.of([]);\n }\n }\n}", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n \n vm.entityId = entityId;\n vm.entityName = entityName;\n vm.attributes = {};\n \n vm.editEntityFormGroup = vm.fb.group({\n entityName: [''],\n attributes: vm.fb.group({\n temperatureAlarmFlag: [false],\n temperatureAlarmThreshold: [{value: null, disabled: true}],\n humidityAlarmFlag: [false],\n humidityAlarmThreshold: [{value: null, disabled: true}]\n })\n });\n \n vm.editEntityFormGroup.get('attributes').get('temperatureAlarmFlag').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.editEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').enable();\n } else {\n vm.editEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').disable();\n }\n });\n \n vm.editEntityFormGroup.get('attributes').get('humidityAlarmFlag').valueChanges\n .subscribe(activate => {\n if (activate) {\n vm.editEntityFormGroup.get('attributes').get('humidityAlarmThreshold').enable();\n } else {\n vm.editEntityFormGroup.get('attributes').get('humidityAlarmThreshold').disable();\n }\n });\n \n \n getEntityInfo();\n \n \n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveAttributes(entityId).subscribe(\n function () {\n vm.dialogRef.close(null);\n }\n );\n };\n \n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n \n function getEntityAttributes(attributes) {\n for (var i = 0; i < attributes.length; i++) {\n vm.attributes[attributes[i].key] = attributes[i].value;\n }\n }\n \n function getEntityInfo() {\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE').subscribe(\n function (attributes) {\n getEntityAttributes(attributes);\n vm.editEntityFormGroup.patchValue({\n entityName: vm.entityName,\n attributes: vm.attributes\n });\n // if(vm.attributes.temperatureAlarmFlag) {\n // vm.editEntityFormGroup.get('attributes').get('temperatureAlarmThreshold').enable();\n // }\n // if(vm.attributes.humidityAlarmFlag) {\n // vm.editEntityFormGroup.get('attributes').get('humidityAlarmThreshold').enable();\n // }\n }\n );\n }\n \n function saveAttributes(entityId) {\n let attributes = vm.editEntityFormGroup.get('attributes').value;\n let attributesArray = [];\n for (let key in attributes) {\n if (attributes[key] !== vm.attributes[key]) {\n attributesArray.push({key: key, value: attributes[key]});\n }\n }\n if (attributesArray.length > 0) {\n return attributeService.saveEntityAttributes(entityId, \"SERVER_SCOPE\", attributesArray);\n } else {\n return widgetContext.rxjs.of([]);\n }\n }\n}", "customResources": [], "id": "7506576f-87ba-d3a0-88fb-e304d451776d" }, @@ -219,8 +219,8 @@ "defaultPageSize": 10, "defaultSortOrder": "-createdTime", "enableSelectColumnDisplay": false, - "enableStatusFilter": true, - "alarmsTitle": "Alarms" + "alarmsTitle": "Alarms", + "enableFilter": true }, "title": "New Alarms table", "dropShadow": true, @@ -234,6 +234,9 @@ "showLegend": false, "alarmSource": { "type": "entity", + "name": "alarms", + "entityAliasId": "68a058e1-fdda-8482-715b-3ae4a488568e", + "filterId": null, "dataKeys": [ { "name": "createdTime", @@ -275,9 +278,7 @@ "settings": {}, "_hash": 0.7977920750136249 } - ], - "entityAliasId": "ce27a9d0-93bf-b7a4-054d-d0369a8cf813", - "name": "alarms" + ] }, "alarmSearchStatus": "ANY", "alarmsPollingInterval": 5, @@ -549,7 +550,7 @@ "type": "entity", "dataKeys": [ { - "name": "alarmTemperature", + "name": "temperatureAlarmFlag", "type": "attribute", "label": "High temperature alarm", "color": "#4caf50", @@ -564,7 +565,7 @@ "_hash": 0.8725278440159361 }, { - "name": "thresholdTemperature", + "name": "temperatureAlarmThreshold", "type": "attribute", "label": "High temperature threshold, °C", "color": "#f44336", @@ -575,12 +576,12 @@ "isEditable": "editable", "dataKeyHidden": false, "step": 1, - "disabledOnDataKey": "alarmTemperature" + "disabledOnDataKey": "temperatureAlarmFlag" }, "_hash": 0.7316078472857874 }, { - "name": "alarmHumidity", + "name": "humidityAlarmFlag", "type": "attribute", "label": "Low humidity alarm", "color": "#ffc107", @@ -595,7 +596,7 @@ "_hash": 0.5339673667431057 }, { - "name": "thresholdHumidity", + "name": "humidityAlarmThreshold", "type": "attribute", "label": "Low humidity threshold, %", "color": "#607d8b", @@ -606,7 +607,7 @@ "isEditable": "editable", "dataKeyHidden": false, "step": 1, - "disabledOnDataKey": "alarmHumidity" + "disabledOnDataKey": "humidityAlarmFlag" }, "_hash": 0.2687091190358901 } @@ -1031,7 +1032,8 @@ "markerImageFunction": "var res;\nif(dsData[dsIndex].active !== \"true\"){\n\tvar res = {\n\t url: images[0],\n\t size: 48\n\t}\n} else {\n var res = {\n\t url: images[1],\n\t size: 48\n\t}\n}\nreturn res;", "useLabelFunction": true, "provider": "openstreet-map", - "draggableMarker": true + "draggableMarker": true, + "editablePolygon": true }, "title": "New Markers Placement - OpenStreetMap", "dropShadow": true, @@ -1062,61 +1064,6 @@ "displayTimewindow": true }, "id": "0a430429-9078-9ae6-2b67-e4a15a2bf8bf" - }, - "f4bb2f2d-0164-60bc-f3e8-9b1e7b5a59b3": { - "isSystemType": true, - "bundleAlias": "input_widgets", - "typeAlias": "update_double_timeseries", - "type": "latest", - "title": "New widget", - "sizeX": 7.5, - "sizeY": 3, - "config": { - "datasources": [ - { - "type": "entity", - "name": null, - "entityAliasId": "12ae98c7-1ea2-52cf-64d5-763e9d993547", - "dataKeys": [ - { - "name": "temperature", - "type": "timeseries", - "label": "temperature", - "color": "#2196f3", - "settings": {}, - "_hash": 0.4164505192982848 - } - ] - } - ], - "timewindow": { - "realtime": { - "timewindowMs": 60000 - } - }, - "showTitle": true, - "backgroundColor": "#fff", - "color": "rgba(0, 0, 0, 0.87)", - "padding": "8px", - "settings": { - "showResultMessage": true, - "showLabel": true - }, - "title": "New Update double timeseries", - "dropShadow": true, - "enableFullscreen": false, - "widgetStyle": {}, - "titleStyle": { - "fontSize": "16px", - "fontWeight": 400 - }, - "useDashboardTimewindow": true, - "showLegend": false, - "actions": {} - }, - "row": 0, - "col": 0, - "id": "f4bb2f2d-0164-60bc-f3e8-9b1e7b5a59b3" } }, "states": { @@ -1215,12 +1162,6 @@ "sizeY": 6, "row": 6, "col": 0 - }, - "f4bb2f2d-0164-60bc-f3e8-9b1e7b5a59b3": { - "sizeX": 7.5, - "sizeY": 3, - "row": 12, - "col": 0 } }, "gridSettings": { @@ -1257,16 +1198,6 @@ "stateEntityParamName": null, "defaultStateEntity": null } - }, - "ce27a9d0-93bf-b7a4-054d-d0369a8cf813": { - "id": "ce27a9d0-93bf-b7a4-054d-d0369a8cf813", - "alias": "Thermostat-alarm", - "filter": { - "type": "entityName", - "resolveMultiple": false, - "entityType": "ASSET", - "entityNameFilter": "Thermostat Alarms" - } } }, "timewindow": { @@ -1301,7 +1232,8 @@ "showDashboardTimewindow": true, "showDashboardExport": true, "toolbarAlwaysOpen": true - } + }, + "filters": {} }, "name": "Thermostats" } \ No newline at end of file diff --git a/application/src/main/data/json/demo/rule_chains/root_rule_chain.json b/application/src/main/data/json/demo/rule_chains/root_rule_chain.json deleted file mode 100644 index 97ad46c756..0000000000 --- a/application/src/main/data/json/demo/rule_chains/root_rule_chain.json +++ /dev/null @@ -1,189 +0,0 @@ -{ - "ruleChain": { - "additionalInfo": null, - "name": "Root Rule Chain", - "firstRuleNodeId": null, - "root": true, - "debugMode": false, - "configuration": null - }, - "metadata": { - "firstNodeIndex": 3, - "nodes": [ - { - "additionalInfo": { - "layoutX": 1069, - "layoutY": 267 - }, - "type": "org.thingsboard.rule.engine.filter.TbJsFilterNode", - "name": "Is Thermostat?", - "debugMode": false, - "configuration": { - "jsScript": "return msg.id.entityType === \"DEVICE\" && msg.type === \"thermostat\";" - } - }, - { - "additionalInfo": { - "layoutX": 824, - "layoutY": 156 - }, - "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", - "name": "Save Timeseries", - "debugMode": false, - "configuration": { - "defaultTTL": 0 - } - }, - { - "additionalInfo": { - "layoutX": 825, - "layoutY": 52 - }, - "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", - "name": "Save Client Attributes", - "debugMode": false, - "configuration": { - "scope": "CLIENT_SCOPE", - "notifyDevice": "false" - } - }, - { - "additionalInfo": { - "layoutX": 347, - "layoutY": 149 - }, - "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode", - "name": "Message Type Switch", - "debugMode": false, - "configuration": { - "version": 0 - } - }, - { - "additionalInfo": { - "layoutX": 839, - "layoutY": 345 - }, - "type": "org.thingsboard.rule.engine.action.TbLogNode", - "name": "Log RPC from Device", - "debugMode": false, - "configuration": { - "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" - } - }, - { - "additionalInfo": { - "layoutX": 832, - "layoutY": 407 - }, - "type": "org.thingsboard.rule.engine.action.TbLogNode", - "name": "Log Other", - "debugMode": false, - "configuration": { - "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" - } - }, - { - "additionalInfo": { - "layoutX": 825, - "layoutY": 468 - }, - "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode", - "name": "RPC Call Request", - "debugMode": false, - "configuration": { - "timeoutInSeconds": 60 - } - }, - { - "additionalInfo": { - "layoutX": 1069, - "layoutY": 90 - }, - "type": "org.thingsboard.rule.engine.filter.TbJsFilterNode", - "name": "Is Thermostat?", - "debugMode": false, - "configuration": { - "jsScript": "return metadata[\"deviceType\"] === \"thermostat\";" - } - }, - { - "additionalInfo": { - "layoutX": 1090, - "layoutY": 360 - }, - "type": "org.thingsboard.rule.engine.action.TbCreateRelationNode", - "name": "Relate to Asset", - "debugMode": false, - "configuration": { - "direction": "FROM", - "relationType": "ToAlarmPropagationAsset", - "entityType": "ASSET", - "entityNamePattern": "Thermostat Alarms", - "entityTypePattern": "AlarmPropagationAsset", - "entityCacheExpiration": 300, - "createEntityIfNotExists": true, - "changeOriginatorToRelatedEntity": false, - "removeCurrentRelations": false - } - } - ], - "connections": [ - { - "fromIndex": 0, - "toIndex": 8, - "type": "True" - }, - { - "fromIndex": 1, - "toIndex": 7, - "type": "Success" - }, - { - "fromIndex": 3, - "toIndex": 5, - "type": "Other" - }, - { - "fromIndex": 3, - "toIndex": 2, - "type": "Post attributes" - }, - { - "fromIndex": 3, - "toIndex": 1, - "type": "Post telemetry" - }, - { - "fromIndex": 3, - "toIndex": 4, - "type": "RPC Request from Device" - }, - { - "fromIndex": 3, - "toIndex": 6, - "type": "RPC Request to Device" - }, - { - "fromIndex": 3, - "toIndex": 0, - "type": "Entity Created" - } - ], - "ruleChainConnections": [ - { - "fromIndex": 7, - "targetRuleChainId": { - "entityType": "RULE_CHAIN", - "id": "25e26570-89ed-11ea-a650-cd6e14e633bd" - }, - "additionalInfo": { - "layoutX": 1109, - "layoutY": 182, - "ruleChainNodeId": "rule-chain-node-10" - }, - "type": "True" - } - ] - } -} \ No newline at end of file diff --git a/application/src/main/data/json/demo/rule_chains/thermostat_alarms.json b/application/src/main/data/json/demo/rule_chains/thermostat_alarms.json deleted file mode 100644 index d67052cbc5..0000000000 --- a/application/src/main/data/json/demo/rule_chains/thermostat_alarms.json +++ /dev/null @@ -1,141 +0,0 @@ -{ - "ruleChain": { - "additionalInfo": null, - "name": "Thermostat Alarms", - "firstRuleNodeId": null, - "root": false, - "debugMode": false, - "configuration": null - }, - "metadata": { - "firstNodeIndex": 5, - "nodes": [ - { - "additionalInfo": { - "layoutX": 929, - "layoutY": 67 - }, - "type": "org.thingsboard.rule.engine.action.TbCreateAlarmNode", - "name": "Create Temp Alarm", - "debugMode": false, - "configuration": { - "alarmType": "High Temperature", - "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\ndetails.triggerValue = msg.temperature;\nreturn details;", - "severity": "MAJOR", - "propagate": true, - "useMessageAlarmData": false, - "relationTypes": [ - "ToAlarmPropagationAsset" - ] - } - }, - { - "additionalInfo": { - "layoutX": 930, - "layoutY": 201 - }, - "type": "org.thingsboard.rule.engine.action.TbClearAlarmNode", - "name": "Clear Temp Alarm", - "debugMode": false, - "configuration": { - "alarmType": "High Temperature", - "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\nreturn details;" - } - }, - { - "additionalInfo": { - "layoutX": 930, - "layoutY": 131 - }, - "type": "org.thingsboard.rule.engine.action.TbCreateAlarmNode", - "name": "Create Humidity Alarm", - "debugMode": false, - "configuration": { - "alarmType": "Low Humidity", - "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\ndetails.triggerValue = msg.humidity;\nreturn details;", - "severity": "MINOR", - "propagate": true, - "useMessageAlarmData": false, - "relationTypes": [ - "ToAlarmPropagationAsset" - ] - } - }, - { - "additionalInfo": { - "layoutX": 929, - "layoutY": 275 - }, - "type": "org.thingsboard.rule.engine.action.TbClearAlarmNode", - "name": "Clear Humidity Alarm", - "debugMode": false, - "configuration": { - "alarmType": "Low Humidity", - "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\nreturn details;" - } - }, - { - "additionalInfo": { - "layoutX": 586, - "layoutY": 148 - }, - "type": "org.thingsboard.rule.engine.filter.TbJsSwitchNode", - "name": "Check Alarms", - "debugMode": false, - "configuration": { - "jsScript": "var relations = [];\nif(metadata[\"ss_alarmTemperature\"] === \"true\"){\n if(msg.temperature > metadata[\"ss_thresholdTemperature\"]){\n relations.push(\"NewTempAlarm\");\n } else {\n relations.push(\"ClearTempAlarm\");\n }\n}\nif(metadata[\"ss_alarmHumidity\"] === \"true\"){\n if(msg.humidity < metadata[\"ss_thresholdHumidity\"]){\n relations.push(\"NewHumidityAlarm\");\n } else {\n relations.push(\"ClearHumidityAlarm\");\n }\n}\n\nreturn relations;" - } - }, - { - "additionalInfo": { - "layoutX": 321, - "layoutY": 149 - }, - "type": "org.thingsboard.rule.engine.metadata.TbGetAttributesNode", - "name": "Fetch Configuration", - "debugMode": false, - "configuration": { - "clientAttributeNames": [], - "sharedAttributeNames": [], - "serverAttributeNames": [ - "alarmTemperature", - "thresholdTemperature", - "alarmHumidity", - "thresholdHumidity" - ], - "latestTsKeyNames": [], - "tellFailureIfAbsent": false, - "getLatestValueWithTs": false - } - } - ], - "connections": [ - { - "fromIndex": 4, - "toIndex": 0, - "type": "NewTempAlarm" - }, - { - "fromIndex": 4, - "toIndex": 1, - "type": "ClearTempAlarm" - }, - { - "fromIndex": 4, - "toIndex": 2, - "type": "NewHumidityAlarm" - }, - { - "fromIndex": 4, - "toIndex": 3, - "type": "ClearHumidityAlarm" - }, - { - "fromIndex": 5, - "toIndex": 4, - "type": "Success" - } - ], - "ruleChainConnections": null - } -} \ No newline at end of file diff --git a/application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json b/application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json index f9d05d83f3..12ffd8b82d 100644 --- a/application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json @@ -18,7 +18,7 @@ "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n hasDataPageLink: true,\n warnOnPageDataOverflow: false,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n },\n 'rowDoubleClick': {\n name: 'widget-action.row-double-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}", "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", - "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true,\"entitiesTitle\":\"Device admin table\",\"enableSelectColumnDisplay\":true},\"title\":\"Device admin table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"name\":\"Add device\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"
\\n \\n

Add device

\\n \\n \\n
\\n \\n \\n
\\n
\\n
\\n \\n Device name\\n \\n \\n Device name is required.\\n \\n \\n
\\n \\n \\n Label\\n \\n \\n
\\n
\\n \\n Latitude\\n \\n \\n \\n Longitude\\n \\n \\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenAddDeviceDialog();\\n\\nfunction openAddDeviceDialog() {\\n customDialog.customDialog(htmlTemplate, AddDeviceDialogController).subscribe();\\n}\\n\\nfunction AddDeviceDialogController(instance) {\\n let vm = instance;\\n \\n vm.addDeviceFormGroup = vm.fb.group({\\n deviceName: ['', [vm.validators.required]],\\n deviceType: ['', [vm.validators.required]],\\n deviceLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.addDeviceFormGroup.markAsPristine();\\n let device = {\\n name: vm.addDeviceFormGroup.get('deviceName').value,\\n type: vm.addDeviceFormGroup.get('deviceType').value,\\n label: vm.addDeviceFormGroup.get('deviceLabel').value\\n };\\n deviceService.saveDevice(device).subscribe(\\n function (device) {\\n saveAttributes(device.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributes = vm.addDeviceFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\"}],\"actionCellButton\":[{\"name\":\"Edit device\",\"icon\":\"edit\",\"type\":\"customPretty\",\"customHtml\":\"
\\n \\n

Edit device

\\n \\n \\n
\\n \\n \\n
\\n
\\n
\\n \\n Device name\\n \\n \\n Device name is required.\\n \\n \\n
\\n \\n \\n Label\\n \\n \\n
\\n
\\n \\n Latitude\\n \\n \\n \\n Longitude\\n \\n \\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenEditDeviceDialog();\\n\\nfunction openEditDeviceDialog() {\\n customDialog.customDialog(htmlTemplate, EditDeviceDialogController).subscribe();\\n}\\n\\nfunction EditDeviceDialogController(instance) {\\n let vm = instance;\\n \\n vm.device = null;\\n vm.attributes = {};\\n \\n vm.editDeviceFormGroup = vm.fb.group({\\n deviceName: ['', [vm.validators.required]],\\n deviceType: ['', [vm.validators.required]],\\n deviceLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.editDeviceFormGroup.markAsPristine();\\n vm.device.name = vm.editDeviceFormGroup.get('deviceName').value,\\n vm.device.type = vm.editDeviceFormGroup.get('deviceType').value,\\n vm.device.label = vm.editDeviceFormGroup.get('deviceLabel').value\\n deviceService.saveDevice(vm.device).subscribe(\\n function () {\\n saveAttributes().subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n deviceService.getDevice(entityId.id).subscribe(\\n function (device) {\\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE',\\n ['latitude', 'longitude']).subscribe(\\n function (attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n vm.device = device;\\n vm.editDeviceFormGroup.patchValue(\\n {\\n deviceName: vm.device.name,\\n deviceType: vm.device.type,\\n deviceLabel: vm.device.label,\\n attributes: {\\n latitude: vm.attributes.latitude,\\n longitude: vm.attributes.longitude\\n }\\n }, {emitEvent: false}\\n );\\n } \\n );\\n }\\n ); \\n }\\n \\n function saveAttributes() {\\n let attributes = vm.editDeviceFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, 'SERVER_SCOPE', attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\"},{\"name\":\"Delete device\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\n\\nopenDeleteDeviceDialog();\\n\\nfunction openDeleteDeviceDialog() {\\n let title = \\\"Are you sure you want to delete the device \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the device and all related data will become unrecoverable!\\\";\\n dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe(\\n function (result) {\\n if (result) {\\n deleteDevice();\\n }\\n }\\n );\\n}\\n\\nfunction deleteDevice() {\\n deviceService.deleteDevice(entityId.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n }\\n );\\n}\\n\",\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\"}]}}" + "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true,\"entitiesTitle\":\"Device admin table\",\"enableSelectColumnDisplay\":true},\"title\":\"Device admin table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"name\":\"Add device\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"
\\n \\n

Add device

\\n \\n \\n
\\n \\n \\n
\\n
\\n
\\n \\n Device name\\n \\n \\n Device name is required.\\n \\n \\n
\\n \\n \\n Label\\n \\n \\n
\\n
\\n \\n Latitude\\n \\n \\n \\n Longitude\\n \\n \\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenAddDeviceDialog();\\n\\nfunction openAddDeviceDialog() {\\n customDialog.customDialog(htmlTemplate, AddDeviceDialogController).subscribe();\\n}\\n\\nfunction AddDeviceDialogController(instance) {\\n let vm = instance;\\n \\n vm.addDeviceFormGroup = vm.fb.group({\\n deviceName: ['', [vm.validators.required]],\\n deviceType: ['', [vm.validators.required]],\\n deviceLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.addDeviceFormGroup.markAsPristine();\\n let device = {\\n name: vm.addDeviceFormGroup.get('deviceName').value,\\n type: vm.addDeviceFormGroup.get('deviceType').value,\\n label: vm.addDeviceFormGroup.get('deviceLabel').value\\n };\\n deviceService.saveDevice(device).subscribe(\\n function (device) {\\n saveAttributes(device.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributes = vm.addDeviceFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\"}],\"actionCellButton\":[{\"name\":\"Edit device\",\"icon\":\"edit\",\"type\":\"customPretty\",\"customHtml\":\"
\\n \\n

Edit device

\\n \\n \\n
\\n \\n \\n
\\n
\\n
\\n \\n Device name\\n \\n \\n Device name is required.\\n \\n \\n
\\n \\n \\n Label\\n \\n \\n
\\n
\\n \\n Latitude\\n \\n \\n \\n Longitude\\n \\n \\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenEditDeviceDialog();\\n\\nfunction openEditDeviceDialog() {\\n customDialog.customDialog(htmlTemplate, EditDeviceDialogController).subscribe();\\n}\\n\\nfunction EditDeviceDialogController(instance) {\\n let vm = instance;\\n \\n vm.device = null;\\n vm.attributes = {};\\n \\n vm.editDeviceFormGroup = vm.fb.group({\\n deviceName: ['', [vm.validators.required]],\\n deviceType: ['', [vm.validators.required]],\\n deviceLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.editDeviceFormGroup.markAsPristine();\\n vm.device.name = vm.editDeviceFormGroup.get('deviceName').value,\\n vm.device.type = vm.editDeviceFormGroup.get('deviceType').value,\\n vm.device.label = vm.editDeviceFormGroup.get('deviceLabel').value\\n deviceService.saveDevice(vm.device).subscribe(\\n function () {\\n saveAttributes().subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n deviceService.getDevice(entityId.id).subscribe(\\n function (device) {\\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE',\\n ['latitude', 'longitude']).subscribe(\\n function (attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n vm.device = device;\\n vm.editDeviceFormGroup.patchValue(\\n {\\n deviceName: vm.device.name,\\n deviceType: vm.device.type,\\n deviceLabel: vm.device.label,\\n attributes: {\\n latitude: vm.attributes.latitude,\\n longitude: vm.attributes.longitude\\n }\\n }, {emitEvent: false}\\n );\\n } \\n );\\n }\\n ); \\n }\\n \\n function saveAttributes() {\\n let attributes = vm.editDeviceFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, 'SERVER_SCOPE', attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\"},{\"name\":\"Delete device\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\n\\nopenDeleteDeviceDialog();\\n\\nfunction openDeleteDeviceDialog() {\\n let title = \\\"Are you sure you want to delete the device \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the device and all related data will become unrecoverable!\\\";\\n dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe(\\n function (result) {\\n if (result) {\\n deleteDevice();\\n }\\n }\\n );\\n}\\n\\nfunction deleteDevice() {\\n deviceService.deleteDevice(entityId.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n }\\n );\\n}\\n\",\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\"}]}}" } }, { @@ -34,8 +34,8 @@ "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n hasDataPageLink: true,\n warnOnPageDataOverflow: false,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n },\n 'rowDoubleClick': {\n name: 'widget-action.row-double-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}", "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", - "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true,\"entitiesTitle\":\"Asset admin table\",\"enableSelectColumnDisplay\":true},\"title\":\"Asset admin table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"name\":\"Add asset\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"
\\n \\n

Add asset

\\n \\n \\n
\\n \\n \\n
\\n
\\n
\\n \\n Asset name\\n \\n \\n Asset name is required.\\n \\n \\n
\\n \\n \\n Label\\n \\n \\n
\\n
\\n \\n Latitude\\n \\n \\n \\n Longitude\\n \\n \\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenAddAssetDialog();\\n\\nfunction openAddAssetDialog() {\\n customDialog.customDialog(htmlTemplate, AddAssetDialogController).subscribe();\\n}\\n\\nfunction AddAssetDialogController(instance) {\\n let vm = instance;\\n \\n vm.addAssetFormGroup = vm.fb.group({\\n assetName: ['', [vm.validators.required]],\\n assetType: ['', [vm.validators.required]],\\n assetLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.addAssetFormGroup.markAsPristine();\\n let asset = {\\n name: vm.addAssetFormGroup.get('assetName').value,\\n type: vm.addAssetFormGroup.get('assetType').value,\\n label: vm.addAssetFormGroup.get('assetLabel').value\\n };\\n assetService.saveAsset(asset).subscribe(\\n function (asset) {\\n saveAttributes(asset.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributes = vm.addAssetFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\"}],\"actionCellButton\":[{\"name\":\"Edit asset\",\"icon\":\"edit\",\"type\":\"customPretty\",\"customHtml\":\"
\\n \\n

Edit asset

\\n \\n \\n
\\n \\n \\n
\\n
\\n
\\n \\n Asset name\\n \\n \\n Asset name is required.\\n \\n \\n
\\n \\n \\n Label\\n \\n \\n
\\n
\\n \\n Latitude\\n \\n \\n \\n Longitude\\n \\n \\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenEditAssetDialog();\\n\\nfunction openEditAssetDialog() {\\n customDialog.customDialog(htmlTemplate, EditAssetDialogController).subscribe();\\n}\\n\\nfunction EditAssetDialogController(instance) {\\n let vm = instance;\\n \\n vm.asset = null;\\n vm.attributes = {};\\n \\n vm.editAssetFormGroup = vm.fb.group({\\n assetName: ['', [vm.validators.required]],\\n assetType: ['', [vm.validators.required]],\\n assetLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.editAssetFormGroup.markAsPristine();\\n vm.asset.name = vm.editAssetFormGroup.get('assetName').value,\\n vm.asset.type = vm.editAssetFormGroup.get('assetType').value,\\n vm.asset.label = vm.editAssetFormGroup.get('assetLabel').value\\n assetService.saveAsset(vm.asset).subscribe(\\n function () {\\n saveAttributes().subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n assetService.getAsset(entityId.id).subscribe(\\n function (asset) {\\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE',\\n ['latitude', 'longitude']).subscribe(\\n function (attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n vm.asset = asset;\\n vm.editAssetFormGroup.patchValue(\\n {\\n assetName: vm.asset.name,\\n assetType: vm.asset.type,\\n assetLabel: vm.asset.label,\\n attributes: {\\n latitude: vm.attributes.latitude,\\n longitude: vm.attributes.longitude\\n }\\n }, {emitEvent: false}\\n );\\n } \\n );\\n }\\n ); \\n }\\n \\n function saveAttributes() {\\n let attributes = vm.editAssetFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, 'SERVER_SCOPE', attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\"},{\"name\":\"Delete asset\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));\\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\\n\\nopenDeleteAssetDialog();\\n\\nfunction openDeleteAssetDialog() {\\n let title = \\\"Are you sure you want to delete the asset \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the asset and all related data will become unrecoverable!\\\";\\n dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe(\\n function (result) {\\n if (result) {\\n deleteAsset();\\n }\\n }\\n );\\n}\\n\\nfunction deleteAsset() {\\n assetService.deleteAsset(entityId.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n }\\n );\\n}\\n\",\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\"}]}}" + "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true,\"entitiesTitle\":\"Asset admin table\",\"enableSelectColumnDisplay\":true},\"title\":\"Asset admin table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"name\":\"Add asset\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"
\\n \\n

Add asset

\\n \\n \\n
\\n \\n \\n
\\n
\\n
\\n \\n Asset name\\n \\n \\n Asset name is required.\\n \\n \\n
\\n \\n \\n Label\\n \\n \\n
\\n
\\n \\n Latitude\\n \\n \\n \\n Longitude\\n \\n \\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenAddAssetDialog();\\n\\nfunction openAddAssetDialog() {\\n customDialog.customDialog(htmlTemplate, AddAssetDialogController).subscribe();\\n}\\n\\nfunction AddAssetDialogController(instance) {\\n let vm = instance;\\n \\n vm.addAssetFormGroup = vm.fb.group({\\n assetName: ['', [vm.validators.required]],\\n assetType: ['', [vm.validators.required]],\\n assetLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.addAssetFormGroup.markAsPristine();\\n let asset = {\\n name: vm.addAssetFormGroup.get('assetName').value,\\n type: vm.addAssetFormGroup.get('assetType').value,\\n label: vm.addAssetFormGroup.get('assetLabel').value\\n };\\n assetService.saveAsset(asset).subscribe(\\n function (asset) {\\n saveAttributes(asset.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributes = vm.addAssetFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\"}],\"actionCellButton\":[{\"name\":\"Edit asset\",\"icon\":\"edit\",\"type\":\"customPretty\",\"customHtml\":\"
\\n \\n

Edit asset

\\n \\n \\n
\\n \\n \\n
\\n
\\n
\\n \\n Asset name\\n \\n \\n Asset name is required.\\n \\n \\n
\\n \\n \\n Label\\n \\n \\n
\\n
\\n \\n Latitude\\n \\n \\n \\n Longitude\\n \\n \\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenEditAssetDialog();\\n\\nfunction openEditAssetDialog() {\\n customDialog.customDialog(htmlTemplate, EditAssetDialogController).subscribe();\\n}\\n\\nfunction EditAssetDialogController(instance) {\\n let vm = instance;\\n \\n vm.asset = null;\\n vm.attributes = {};\\n \\n vm.editAssetFormGroup = vm.fb.group({\\n assetName: ['', [vm.validators.required]],\\n assetType: ['', [vm.validators.required]],\\n assetLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.editAssetFormGroup.markAsPristine();\\n vm.asset.name = vm.editAssetFormGroup.get('assetName').value,\\n vm.asset.type = vm.editAssetFormGroup.get('assetType').value,\\n vm.asset.label = vm.editAssetFormGroup.get('assetLabel').value\\n assetService.saveAsset(vm.asset).subscribe(\\n function () {\\n saveAttributes().subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n assetService.getAsset(entityId.id).subscribe(\\n function (asset) {\\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE',\\n ['latitude', 'longitude']).subscribe(\\n function (attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n vm.asset = asset;\\n vm.editAssetFormGroup.patchValue(\\n {\\n assetName: vm.asset.name,\\n assetType: vm.asset.type,\\n assetLabel: vm.asset.label,\\n attributes: {\\n latitude: vm.attributes.latitude,\\n longitude: vm.attributes.longitude\\n }\\n }, {emitEvent: false}\\n );\\n } \\n );\\n }\\n ); \\n }\\n \\n function saveAttributes() {\\n let attributes = vm.editAssetFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, 'SERVER_SCOPE', attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\"},{\"name\":\"Delete asset\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));\\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\\n\\nopenDeleteAssetDialog();\\n\\nfunction openDeleteAssetDialog() {\\n let title = \\\"Are you sure you want to delete the asset \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the asset and all related data will become unrecoverable!\\\";\\n dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe(\\n function (result) {\\n if (result) {\\n deleteAsset();\\n }\\n }\\n );\\n}\\n\\nfunction deleteAsset() {\\n assetService.deleteAsset(entityId.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n }\\n );\\n}\\n\",\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\"}]}}" } } ] -} \ No newline at end of file +} diff --git a/application/src/main/data/json/system/widget_bundles/input_widgets.json b/application/src/main/data/json/system/widget_bundles/input_widgets.json index 097c31dac4..4ed3c2fab6 100644 --- a/application/src/main/data/json/system/widget_bundles/input_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/input_widgets.json @@ -33,7 +33,7 @@ "templateCss": ".tb-toast {\n min-width: 0;\n font-size: 14px !important;\n}", "controllerScript": "self.onInit = function() {\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n self.ctx.$scope.multipleInputWidget.onDataUpdated();\r\n}\r\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"MultipleInput\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showActionButtons\":{\n \"title\":\"Show action buttons\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"updateAllValues\": {\n \"title\":\"Update all values, not only modified\",\n \"type\":\"boolean\",\n \"default\": false\n },\n \"saveButtonLabel\": {\n \"title\": \"'SAVE' button label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"resetButtonLabel\": {\n \"title\": \"'UNDO' button label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"showGroupTitle\": {\n \"title\":\"Show title for group of fields, related to different entities\",\n \"type\":\"boolean\",\n \"default\": false\n },\n \"groupTitle\": {\n \"title\": \"Group title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"fieldsAlignment\": {\n \"title\": \"Fields alignment\",\n \"type\": \"string\",\n \"default\": \"row\"\n },\n \"fieldsInRow\": {\n \"title\": \"Number of fields in the row\",\n \"type\": \"number\",\n \"default\": \"2\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showActionButtons\",\n {\n \"key\": \"updateAllValues\",\n \"condition\": \"model.showActionButtons === true\"\n },\n {\n \"key\": \"saveButtonLabel\",\n \"condition\": \"model.showActionButtons === true\"\n },\n {\n \"key\": \"resetButtonLabel\",\n \"condition\": \"model.showActionButtons === true\"\n },\n \"showResultMessage\",\n \"showGroupTitle\",\n \"groupTitle\",\n {\n \"key\": \"fieldsAlignment\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"row\",\n \"label\": \"Row (default)\"\n },\n {\n \"value\": \"column\",\n \"label\": \"Column\"\n }\n ]\n },\n {\n \"key\": \"fieldsInRow\",\n \"condition\": \"model.fieldsAlignment === 'row'\"\n }\n ]\n}", - "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"dataKeyType\": {\n \"title\": \"Datakey type\",\n \"type\": \"string\",\n \"default\": \"server\"\n },\n \"dataKeyValueType\": {\n \"title\": \"Datakey value type\",\n \"type\": \"string\",\n \"default\": \"string\"\n },\n \"step\": {\n \"title\": \"Step interval between values\",\n \"type\": \"number\",\n \"default\": \"1\"\n },\n \"minValue\": {\n \"title\": \"Minimum value\",\n \"type\": \"number\"\n },\n \"maxValue\": {\n \"title\": \"Maximum value\",\n \"type\": \"number\"\n },\n \"required\": {\n \"title\": \"Value is required\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"minValueErrorMessage\": {\n \"title\": \"'Min Value' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValueErrorMessage\": {\n \"title\": \"'Max Value' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"invalidDateErrorMessage\": {\n \"title\": \"'Invalid Date' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"selectOptionsType\": {\n \"title\": \"Select options type\",\n \"type\": \"string\",\n \"default\": \"valueWithLabel\"\n },\n \"selectOptionsList\": {\n \"title\": \"Select options list\",\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"value\": {\n \"title\": \"Value\",\n \"type\": \"string\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n }\n },\n \"required\": [\"value\", \"label\"]\n }\n },\n \"isEditable\": {\n \"title\": \"Ability to edit attribute\",\n \"type\": \"string\",\n \"default\": \"editable\"\n },\n \"disabledOnDataKey\": {\n \"title\": \"Disable on false value of another datakey (specify datakey name)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"dataKeyHidden\": {\n \"title\": \"Hide input field\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"icon\": {\n \"title\": \"Icon to show before input cell\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"dataKeyType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"server\",\n \"label\": \"Server attribute (default)\"\n },\n {\n \"value\": \"shared\",\n \"label\": \"Shared attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Timeseries\"\n }\n ]\n },\n {\n \"key\": \"dataKeyValueType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"string\",\n \"label\": \"String\"\n },\n {\n \"value\": \"double\",\n \"label\": \"Double\"\n },\n {\n \"value\": \"integer\",\n \"label\": \"Integer\"\n },\n {\n \"value\": \"booleanCheckbox\",\n \"label\": \"Boolean (Checkbox)\"\n },\n {\n \"value\": \"booleanSwitch\",\n \"label\": \"Boolean (Switch)\"\n },\n {\n \"value\": \"dateTime\",\n \"label\": \"Date & Time\"\n },\n {\n \"value\": \"date\",\n \"label\": \"Date\"\n },\n {\n \"value\": \"time\",\n \"label\": \"Time\"\n },\n {\n \"value\": \"selectOption\",\n \"label\": \"Selectable option\"\n }\n ]\n },\n {\n \"key\": \"selectOptionsType\",\n \"condition\": \"model.dataKeyValueType === 'selectOption'\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"valueWithLabel\",\n \"label\": \"Values with labels\"\n },\n {\n \"value\": \"rawValue\",\n \"label\": \"Raw values\"\n }\n ]\n },\n {\n \"key\": \"selectOptionsList\",\n \"type\": \"array\",\n \"condition\": \"model.dataKeyValueType === 'selectOption'\",\n \"items\": [\n \"selectOptionsList[].value\",\n {\n \"key\": \"selectOptionsList[].label\",\n \"condition\": \"model.selectOptionsType === 'valueWithLabel'\"\n }\n ]\n },\n {\n \"key\": \"step\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"minValue\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"maxValue\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n \"required\",\n {\n \"key\": \"requiredErrorMessage\",\n \"condition\": \"model.required === true\"\n },\n {\n \"key\": \"invalidDateErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'dateTime' || model.dataKeyValueType === 'date' || model.dataKeyValueType === 'time'\"\n },\n {\n \"key\": \"minValueErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"maxValueErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"isEditable\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"editable\",\n \"label\": \"Editable (default)\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n },\n {\n \"value\": \"readonly\",\n \"label\": \"Read-only\"\n }\n ]\n },\n \"disabledOnDataKey\",\n \"dataKeyHidden\",\n\t\t{\n \t\t\"key\": \"icon\",\n\t\t\t\"type\": \"icon\"\n\t\t}\n ]\n}\n", + "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"dataKeyType\": {\n \"title\": \"Datakey type\",\n \"type\": \"string\",\n \"default\": \"server\"\n },\n \"dataKeyValueType\": {\n \"title\": \"Datakey value type\",\n \"type\": \"string\",\n \"default\": \"string\"\n },\n \"step\": {\n \"title\": \"Step interval between values\",\n \"type\": \"number\",\n \"default\": \"1\"\n },\n \"minValue\": {\n \"title\": \"Minimum value\",\n \"type\": \"number\"\n },\n \"maxValue\": {\n \"title\": \"Maximum value\",\n \"type\": \"number\"\n },\n \"required\": {\n \"title\": \"Value is required\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"minValueErrorMessage\": {\n \"title\": \"'Min Value' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValueErrorMessage\": {\n \"title\": \"'Max Value' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"invalidDateErrorMessage\": {\n \"title\": \"'Invalid Date' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"isEditable\": {\n \"title\": \"Ability to edit attribute\",\n \"type\": \"string\",\n \"default\": \"editable\"\n },\n \"disabledOnDataKey\": {\n \"title\": \"Disable on false value of another datakey (specify datakey name)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"dataKeyHidden\": {\n \"title\": \"Hide input field\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"icon\": {\n \"title\": \"Icon to show before input cell\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"dataKeyType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"server\",\n \"label\": \"Server attribute (default)\"\n },\n {\n \"value\": \"shared\",\n \"label\": \"Shared attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Timeseries\"\n }\n ]\n },\n {\n \"key\": \"dataKeyValueType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"string\",\n \"label\": \"String\"\n },\n {\n \"value\": \"double\",\n \"label\": \"Double\"\n },\n {\n \"value\": \"integer\",\n \"label\": \"Integer\"\n },\n {\n \"value\": \"booleanCheckbox\",\n \"label\": \"Boolean (Checkbox)\"\n },\n {\n \"value\": \"booleanSwitch\",\n \"label\": \"Boolean (Switch)\"\n },\n {\n \"value\": \"dateTime\",\n \"label\": \"Date & Time\"\n },\n {\n \"value\": \"date\",\n \"label\": \"Date\"\n },\n {\n \"value\": \"time\",\n \"label\": \"Time\"\n }\n ]\n },\n {\n \"key\": \"step\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"minValue\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"maxValue\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n \"required\",\n {\n \"key\": \"requiredErrorMessage\",\n \"condition\": \"model.required === true\"\n },\n {\n \"key\": \"invalidDateErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'dateTime' || model.dataKeyValueType === 'date' || model.dataKeyValueType === 'time'\"\n },\n {\n \"key\": \"minValueErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"maxValueErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"isEditable\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"editable\",\n \"label\": \"Editable (default)\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n },\n {\n \"value\": \"readonly\",\n \"label\": \"Read-only\"\n }\n ]\n },\n \"disabledOnDataKey\",\n \"dataKeyHidden\",\n\t\t{\n \t\t\"key\": \"icon\",\n\t\t\t\"type\": \"icon\"\n\t\t}\n ]\n}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.23592248334107624,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update Multiple Attributes\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" } }, diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index 52dbb38c9c..49119ed667 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -32,6 +32,8 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.thingsboard.rule.engine.api.MailService; +import org.thingsboard.rule.engine.api.SmsService; +import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.actors.tenant.DebugTbRateLimits; import org.thingsboard.server.common.data.DataConstants; @@ -80,6 +82,7 @@ import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService; import org.thingsboard.server.service.script.JsExecutorService; import org.thingsboard.server.service.script.JsInvokeService; import org.thingsboard.server.service.session.DeviceSessionCacheService; +import org.thingsboard.server.service.sms.SmsExecutorService; import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; @@ -228,6 +231,10 @@ public class ActorSystemContext { @Getter private MailExecutorService mailExecutor; + @Autowired + @Getter + private SmsExecutorService smsExecutor; + @Autowired @Getter private DbCallbackExecutorService dbCallbackExecutor; @@ -244,6 +251,14 @@ public class ActorSystemContext { @Getter private MailService mailService; + @Autowired + @Getter + private SmsService smsService; + + @Autowired + @Getter + private SmsSenderFactory smsSenderFactory; + @Autowired @Getter private ClaimDevicesService claimDevicesService; @@ -325,6 +340,10 @@ public class ActorSystemContext { @Getter private boolean allowSystemMailService; + @Value("${actors.rule.allow_system_sms_service}") + @Getter + private boolean allowSystemSmsService; + @Value("${transport.sessions.inactivity_timeout}") @Getter private long sessionInactivityTimeout; diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 6749ed69ed..0884148e4f 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -28,14 +28,18 @@ import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache; import org.thingsboard.rule.engine.api.RuleEngineRpcService; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.ScriptEngine; +import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbRelationTypes; +import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.TbActorRef; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.Customer; 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.TenantProfile; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.DeviceId; @@ -240,8 +244,18 @@ class DefaultTbContext implements TbContext { if (nodeCtx.getSelf().isDebugMode()) { mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, TbRelationTypes.FAILURE, th); } + String failureMessage; + if (th != null) { + if (!StringUtils.isEmpty(th.getMessage())) { + failureMessage = th.getMessage(); + } else { + failureMessage = th.getClass().getSimpleName(); + } + } else { + failureMessage = null; + } nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), Collections.singleton(TbRelationTypes.FAILURE), - msg, th != null ? th.getMessage() : null)); + msg, failureMessage)); } public void updateSelf(RuleNode self) { @@ -302,6 +316,11 @@ class DefaultTbContext implements TbContext { return mainCtx.getMailExecutor(); } + @Override + public ListeningExecutor getSmsExecutor() { + return mainCtx.getSmsExecutor(); + } + @Override public ListeningExecutor getDbCallbackExecutor() { return mainCtx.getDbCallbackExecutor(); @@ -427,6 +446,20 @@ class DefaultTbContext implements TbContext { } } + @Override + public SmsService getSmsService() { + if (mainCtx.isAllowSystemSmsService()) { + return mainCtx.getSmsService(); + } else { + throw new RuntimeException("Access to System SMS Service is forbidden!"); + } + } + + @Override + public SmsSenderFactory getSmsSenderFactory() { + return mainCtx.getSmsSenderFactory(); + } + @Override public RuleEngineRpcService getRpcService() { return mainCtx.getTbRuleEngineDeviceRpcService(); @@ -488,14 +521,25 @@ class DefaultTbContext implements TbContext { mainCtx.getRuleNodeStateService().removeByRuleNodeIdAndEntityId(getTenantId(), getSelfId(), entityId); } + @Override + public void addTenantProfileListener(Consumer listener) { + mainCtx.getTenantProfileCache().addListener(getTenantId(), getSelfId(), listener); + } + @Override public void addDeviceProfileListeners(Consumer profileListener, BiConsumer deviceListener) { mainCtx.getDeviceProfileCache().addListener(getTenantId(), getSelfId(), profileListener, deviceListener); } @Override - public void removeProfileListener() { + public void removeListeners() { mainCtx.getDeviceProfileCache().removeListener(getTenantId(), getSelfId()); + mainCtx.getTenantProfileCache().removeListener(getTenantId(), getSelfId()); + } + + @Override + public TenantProfile getTenantProfile() { + return mainCtx.getTenantProfileCache().get(getTenantId()); } private TbMsgMetaData getActionMetaData(RuleNodeId ruleNodeId) { diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java index ad5f97ab48..8cf2859f5a 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java @@ -56,6 +56,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; import java.util.stream.Collectors; /** @@ -288,10 +289,10 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor getTimeseriesKeys( + @RequestParam(name = DEVICE_PROFILE_ID, required = false) String deviceProfileIdStr) throws ThingsboardException { + DeviceProfileId deviceProfileId; + if (StringUtils.isNotEmpty(deviceProfileIdStr)) { + deviceProfileId = new DeviceProfileId(UUID.fromString(deviceProfileIdStr)); + checkDeviceProfileId(deviceProfileId, Operation.READ); + } else { + deviceProfileId = null; + } + + try { + return timeseriesService.findAllKeysByDeviceProfileId(getTenantId(), deviceProfileId); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/deviceProfile/devices/keys/attributes", method = RequestMethod.GET) + @ResponseBody + public List getAttributesKeys( + @RequestParam(name = DEVICE_PROFILE_ID, required = false) String deviceProfileIdStr) throws ThingsboardException { + DeviceProfileId deviceProfileId; + if (StringUtils.isNotEmpty(deviceProfileIdStr)) { + deviceProfileId = new DeviceProfileId(UUID.fromString(deviceProfileIdStr)); + checkDeviceProfileId(deviceProfileId, Operation.READ); + } else { + deviceProfileId = null; + } + + try { + return attributesService.findAllKeysByDeviceProfileId(getTenantId(), deviceProfileId); + } catch (Exception e) { + throw handleException(e); + } + } + @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/deviceProfile", method = RequestMethod.POST) @ResponseBody @@ -113,8 +164,8 @@ public class DeviceProfileController extends BaseController { @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/deviceProfile/{deviceProfileId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) - public void deleteDeviceProfile(@PathVariable("deviceProfileId") String strDeviceProfileId) throws ThingsboardException { - checkParameter("deviceProfileId", strDeviceProfileId); + public void deleteDeviceProfile(@PathVariable(DEVICE_PROFILE_ID) String strDeviceProfileId) throws ThingsboardException { + checkParameter(DEVICE_PROFILE_ID, strDeviceProfileId); try { DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId)); DeviceProfile deviceProfile = checkDeviceProfileId(deviceProfileId, Operation.DELETE); @@ -139,8 +190,8 @@ public class DeviceProfileController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/deviceProfile/{deviceProfileId}/default", method = RequestMethod.POST) @ResponseBody - public DeviceProfile setDefaultDeviceProfile(@PathVariable("deviceProfileId") String strDeviceProfileId) throws ThingsboardException { - checkParameter("deviceProfileId", strDeviceProfileId); + public DeviceProfile setDefaultDeviceProfile(@PathVariable(DEVICE_PROFILE_ID) String strDeviceProfileId) throws ThingsboardException { + checkParameter(DEVICE_PROFILE_ID, strDeviceProfileId); try { DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId)); DeviceProfile deviceProfile = checkDeviceProfileId(deviceProfileId, Operation.WRITE); diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index c8a30f8960..dc8bae5a08 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -45,6 +45,7 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.DeviceId; @@ -69,6 +70,7 @@ import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -93,6 +95,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** @@ -205,7 +208,7 @@ public class TelemetryController extends BaseController { @RequestParam(name = "interval", defaultValue = "0") Long interval, @RequestParam(name = "limit", defaultValue = "100") Integer limit, @RequestParam(name = "agg", defaultValue = "NONE") String aggStr, - @RequestParam(name= "orderBy", defaultValue = "DESC") String orderBy, + @RequestParam(name = "orderBy", defaultValue = "DESC") String orderBy, @RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException { return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr, (result, tenantId, entityId) -> { @@ -392,7 +395,7 @@ public class TelemetryController extends BaseController { if (attributes.isEmpty()) { return getImmediateDeferredResult("No attributes data found in request body!", HttpStatus.BAD_REQUEST); } - for (AttributeKvEntry attributeKvEntry: attributes) { + for (AttributeKvEntry attributeKvEntry : attributes) { if (attributeKvEntry.getKey().isEmpty() || attributeKvEntry.getKey().trim().length() == 0) { return getImmediateDeferredResult("Key cannot be empty or contains only spaces", HttpStatus.BAD_REQUEST); } @@ -440,9 +443,13 @@ public class TelemetryController extends BaseController { if (entries.isEmpty()) { return getImmediateDeferredResult("No timeseries data found in request body!", HttpStatus.BAD_REQUEST); } - SecurityUser user = getCurrentUser(); return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_TELEMETRY, entityIdSrc, (result, tenantId, entityId) -> { - tsSubService.saveAndNotify(tenantId, entityId, entries, ttl, new FutureCallback() { + long tenantTtl = ttl; + if (!TenantId.SYS_TENANT_ID.equals(tenantId) && tenantTtl == 0) { + TenantProfile tenantProfile = tenantProfileCache.get(tenantId); + tenantTtl = TimeUnit.DAYS.toSeconds(((DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration()).getDefaultStorageTtlDays()); + } + tsSubService.saveAndNotify(tenantId, entityId, entries, tenantTtl, new FutureCallback() { @Override public void onSuccess(@Nullable Void tmp) { logTelemetryUpdated(user, entityId, entries, null); 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 c5afb06483..65b7039a71 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -186,6 +186,10 @@ public class ThingsboardInstallService { systemDataLoaderService.updateSystemWidgets(); systemDataLoaderService.createOAuth2Templates(); break; + case "3.2.0": + log.info("Upgrading ThingsBoard from version 3.2.0 to 3.2.1 ..."); + databaseEntitiesUpgradeService.upgradeDatabase("3.2.0"); + break; default: throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion); diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index c7206712c3..12e8e6017d 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -17,21 +17,21 @@ package org.thingsboard.server.service.apiusage; import com.google.common.util.concurrent.FutureCallback; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.annotation.Lazy; -import org.springframework.context.event.EventListener; -import org.springframework.core.annotation.Order; -import org.springframework.data.util.Pair; import org.springframework.stereotype.Service; +import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.server.common.data.ApiFeature; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.ApiUsageStateMailMessage; import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.ApiUsageStateId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; @@ -45,6 +45,7 @@ import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.tools.SchedulerUtils; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; @@ -54,14 +55,13 @@ import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.scheduler.SchedulerComponent; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.telemetry.InternalTelemetryService; import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -69,9 +69,12 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; @Slf4j @Service @@ -94,6 +97,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { private final ApiUsageStateService apiUsageStateService; private final SchedulerComponent scheduler; private final TbTenantProfileCache tenantProfileCache; + private final MailService mailService; @Lazy @Autowired @@ -112,13 +116,15 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { private final Lock updateLock = new ReentrantLock(); + private final ExecutorService mailExecutor; + public DefaultTbApiUsageStateService(TbClusterService clusterService, PartitionService partitionService, TenantService tenantService, TimeseriesService tsService, ApiUsageStateService apiUsageStateService, SchedulerComponent scheduler, - TbTenantProfileCache tenantProfileCache) { + TbTenantProfileCache tenantProfileCache, MailService mailService) { this.clusterService = clusterService; this.partitionService = partitionService; this.tenantService = tenantService; @@ -126,6 +132,8 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { this.apiUsageStateService = apiUsageStateService; this.scheduler = scheduler; this.tenantProfileCache = tenantProfileCache; + this.mailService = mailService; + this.mailExecutor = Executors.newSingleThreadExecutor(); } @PostConstruct @@ -141,6 +149,11 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { public void process(TbProtoQueueMsg msg, TbCallback callback) { ToUsageStatsServiceMsg statsMsg = msg.getValue(); TenantId tenantId = new TenantId(new UUID(statsMsg.getTenantIdMSB(), statsMsg.getTenantIdLSB())); + + if (tenantProfileCache.get(tenantId) == null) { + return; + } + TenantApiUsageState tenantState; List updatedEntries; Map result; @@ -160,7 +173,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { long newValue = tenantState.add(recordKey, kvProto.getValue()); updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.getApiCountKey(), newValue))); long newHourlyValue = tenantState.addToHourly(recordKey, kvProto.getValue()); - updatedEntries.add(new BasicTsKvEntry(hourTs, new LongDataEntry(recordKey.getApiCountKey() + HOURLY, newHourlyValue))); + updatedEntries.add(new BasicTsKvEntry(newHourTs, new LongDataEntry(recordKey.getApiCountKey() + HOURLY, newHourlyValue))); apiFeatures.add(recordKey.getApiFeature()); } result = tenantState.checkStateUpdatedDueToThreshold(apiFeatures); @@ -286,7 +299,49 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { List stateTelemetry = new ArrayList<>(); result.forEach(((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(apiFeature.getApiStateKey(), aState.name()))))); tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, VOID_CALLBACK); - //TODO: notify tenant admin via email! + + String email = tenantService.findTenantById(state.getTenantId()).getEmail(); + + if (StringUtils.isNotEmpty(email)) { + result.forEach((apiFeature, stateValue) -> { + mailExecutor.submit(() -> { + try { + mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, createStateMailMessage(state, apiFeature, stateValue)); + } catch (ThingsboardException e) { + log.warn("[{}] Can't send update of the API state to tenant with provided email [{}]", state.getTenantId(), email, e); + } + }); + }); + } else { + log.warn("[{}] Can't send update of the API state to tenant with empty email!", state.getTenantId()); + } + } + + private ApiUsageStateMailMessage createStateMailMessage(TenantApiUsageState state, ApiFeature apiFeature, ApiUsageStateValue stateValue) { + StateChecker checker = getStateChecker(stateValue); + for (ApiUsageRecordKey apiUsageRecordKey : ApiUsageRecordKey.getKeys(apiFeature)) { + long threshold = state.getProfileThreshold(apiUsageRecordKey); + long warnThreshold = state.getProfileWarnThreshold(apiUsageRecordKey); + long value = state.get(apiUsageRecordKey); + if (checker.check(threshold, warnThreshold, value)) { + return new ApiUsageStateMailMessage(apiUsageRecordKey, threshold, value); + } + } + return null; + } + + private StateChecker getStateChecker(ApiUsageStateValue stateValue) { + if (ApiUsageStateValue.ENABLED.equals(stateValue)) { + return (t, wt, v) -> true; + } else if (ApiUsageStateValue.WARNING.equals(stateValue)) { + return (t, wt, v) -> v < t && v >= wt; + } else { + return (t, wt, v) -> v >= t; + } + } + + private interface StateChecker { + boolean check(long threshold, long warnThreshold, long value); } private void checkStartOfNextCycle() { @@ -294,8 +349,11 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { try { long now = System.currentTimeMillis(); myTenantStates.values().forEach(state -> { - if ((state.getNextCycleTs() > now) && (state.getNextCycleTs() - now < TimeUnit.HOURS.toMillis(1))) { + if ((state.getNextCycleTs() < now) && (now - state.getNextCycleTs() < TimeUnit.HOURS.toMillis(1))) { + TenantId tenantId = state.getTenantId(); state.setCycles(state.getNextCycleTs(), SchedulerUtils.getStartOfNextNextMonth()); + saveNewCounts(state, Arrays.asList(ApiUsageRecordKey.values())); + updateTenantState(state, tenantProfileCache.get(tenantId)); } }); } finally { @@ -303,6 +361,14 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } + private void saveNewCounts(TenantApiUsageState state, List keys) { + List counts = keys.stream() + .map(key -> new BasicTsKvEntry(state.getCurrentCycleTs(), new LongDataEntry(key.getApiCountKey(), 0L))) + .collect(Collectors.toList()); + + tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), counts, VOID_CALLBACK); + } + private TenantApiUsageState getOrFetchState(TenantId tenantId) { TenantApiUsageState tenantState = myTenantStates.get(tenantId); if (tenantState == null) { @@ -316,6 +382,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } TenantProfile tenantProfile = tenantProfileCache.get(tenantId); tenantState = new TenantApiUsageState(tenantProfile, dbStateEntity); + List newCounts = new ArrayList<>(); try { List dbValues = tsService.findAllLatest(tenantId, dbStateEntity.getId()).get(); for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { @@ -324,7 +391,13 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { for (TsKvEntry tsKvEntry : dbValues) { if (tsKvEntry.getKey().equals(key.getApiCountKey())) { cycleEntryFound = true; - tenantState.put(key, tsKvEntry.getTs() == tenantState.getCurrentCycleTs() ? tsKvEntry.getLongValue().get() : 0L); + + boolean oldCount = tsKvEntry.getTs() == tenantState.getCurrentCycleTs(); + tenantState.put(key, oldCount ? tsKvEntry.getLongValue().get() : 0L); + + if (!oldCount) { + newCounts.add(key); + } } else if (tsKvEntry.getKey().equals(key.getApiCountKey() + HOURLY)) { hourlyEntryFound = true; tenantState.putHourly(key, tsKvEntry.getTs() == tenantState.getCurrentHourTs() ? tsKvEntry.getLongValue().get() : 0L); @@ -336,6 +409,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } log.debug("[{}] Initialized state: {}", tenantId, dbStateEntity); myTenantStates.put(tenantId, tenantState); + saveNewCounts(tenantState, newCounts); } catch (InterruptedException | ExecutionException e) { log.warn("[{}] Failed to fetch api usage state from db.", tenantId, e); } @@ -367,4 +441,10 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } + @PreDestroy + private void destroy() { + if (mailExecutor != null) { + mailExecutor.shutdownNow(); + } + } } diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java index a43afcd2c5..a8e229933f 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java @@ -125,6 +125,10 @@ public class TenantApiUsageState { return apiUsageState.getDbStorageState(); case JS: return apiUsageState.getJsExecState(); + case EMAIL: + return apiUsageState.getEmailExecState(); + case SMS: + return apiUsageState.getSmsExecState(); default: return ApiUsageStateValue.ENABLED; } @@ -145,6 +149,12 @@ public class TenantApiUsageState { case JS: apiUsageState.setJsExecState(value); break; + case EMAIL: + apiUsageState.setEmailExecState(value); + break; + case SMS: + apiUsageState.setSmsExecState(value); + break; } return !currentValue.equals(value); } @@ -171,7 +181,7 @@ public class TenantApiUsageState { long threshold = getProfileThreshold(recordKey); long warnThreshold = getProfileWarnThreshold(recordKey); ApiUsageStateValue tmpValue; - if (threshold == 0 || value < warnThreshold) { + if (threshold == 0 || value == 0 || value < warnThreshold) { tmpValue = ApiUsageStateValue.ENABLED; } else if (value < threshold) { tmpValue = ApiUsageStateValue.WARNING; diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index d372b878f7..2a2f1bd03a 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -18,10 +18,9 @@ package org.thingsboard.server.service.device; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.RandomStringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; @@ -113,6 +112,13 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { public ProvisionResponse provisionDevice(ProvisionRequest provisionRequest) { String provisionRequestKey = provisionRequest.getCredentials().getProvisionDeviceKey(); String provisionRequestSecret = provisionRequest.getCredentials().getProvisionDeviceSecret(); + if (!StringUtils.isEmpty(provisionRequest.getDeviceName())) { + provisionRequest.setDeviceName(provisionRequest.getDeviceName().trim()); + if (StringUtils.isEmpty(provisionRequest.getDeviceName())) { + log.warn("Provision request contains empty device name!"); + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); + } + } if (StringUtils.isEmpty(provisionRequestKey) || StringUtils.isEmpty(provisionRequestSecret)) { throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); @@ -188,6 +194,11 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName()); try { if (device == null) { + if (StringUtils.isEmpty(provisionRequest.getDeviceName())) { + String newDeviceName = RandomStringUtils.randomAlphanumeric(20); + log.info("Device name not found in provision request. Generated name is: {}", newDeviceName); + provisionRequest.setDeviceName(newDeviceName); + } Device savedDevice = deviceService.saveDevice(provisionRequest, profile); deviceStateService.onDeviceAdded(savedDevice); diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 6c7678f023..c51420c8e0 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -28,12 +28,21 @@ import org.thingsboard.server.common.data.Customer; 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.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.DeviceProfileType; +import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; -import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.alarm.AlarmSeverity; +import org.thingsboard.server.common.data.device.profile.AlarmCondition; +import org.thingsboard.server.common.data.device.profile.AlarmRule; +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; +import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -42,19 +51,29 @@ import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; -import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.query.BooleanFilterPredicate; +import org.thingsboard.server.common.data.query.DynamicValue; +import org.thingsboard.server.common.data.query.DynamicValueSourceType; +import org.thingsboard.server.common.data.query.EntityKey; +import org.thingsboard.server.common.data.query.EntityKeyType; +import org.thingsboard.server.common.data.query.EntityKeyValueType; +import org.thingsboard.server.common.data.query.FilterPredicateValue; +import org.thingsboard.server.common.data.query.KeyFilter; +import org.thingsboard.server.common.data.query.NumericFilterPredicate; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.UserCredentials; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.widget.WidgetsBundle; -import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.relation.RelationService; +import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; @@ -62,6 +81,8 @@ import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.widget.WidgetsBundleService; import java.util.Arrays; +import java.util.Collections; +import java.util.TreeMap; @Service @Profile("install") @@ -96,12 +117,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @Autowired private CustomerService customerService; - @Autowired - private RelationService relationService; - - @Autowired - private AssetService assetService; - @Autowired private DeviceService deviceService; @@ -114,6 +129,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @Autowired private DeviceCredentialsService deviceCredentialsService; + @Autowired + private RuleChainService ruleChainService; + @Bean protected BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); @@ -245,35 +263,149 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { createDevice(demoTenant.getId(), null, defaultDeviceProfile.getId(), "Raspberry Pi Demo Device", "RASPBERRY_PI_DEMO_TOKEN", "Demo device that is used in " + "Raspberry Pi GPIO control sample application"); - Asset thermostatAlarms = new Asset(); - thermostatAlarms.setTenantId(demoTenant.getId()); - thermostatAlarms.setName("Thermostat Alarms"); - thermostatAlarms.setType("AlarmPropagationAsset"); - thermostatAlarms = assetService.saveAsset(thermostatAlarms); - - DeviceProfile thermostatDeviceProfile = this.deviceProfileService.findOrCreateDeviceProfile(demoTenant.getId(), "thermostat"); - - DeviceId t1Id = createDevice(demoTenant.getId(), null, thermostatDeviceProfile.getId(), "Thermostat T1", "T1_TEST_TOKEN", "Demo device for Thermostats dashboard").getId(); - DeviceId t2Id = createDevice(demoTenant.getId(), null, thermostatDeviceProfile.getId(), "Thermostat T2", "T2_TEST_TOKEN", "Demo device for Thermostats dashboard").getId(); - - relationService.saveRelation(thermostatAlarms.getTenantId(), new EntityRelation(thermostatAlarms.getId(), t1Id, "ToAlarmPropagationAsset")); - relationService.saveRelation(thermostatAlarms.getTenantId(), new EntityRelation(thermostatAlarms.getId(), t2Id, "ToAlarmPropagationAsset")); + DeviceProfile thermostatDeviceProfile = new DeviceProfile(); + thermostatDeviceProfile.setTenantId(demoTenant.getId()); + thermostatDeviceProfile.setDefault(false); + thermostatDeviceProfile.setName("thermostat"); + thermostatDeviceProfile.setType(DeviceProfileType.DEFAULT); + thermostatDeviceProfile.setTransportType(DeviceTransportType.DEFAULT); + thermostatDeviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); + thermostatDeviceProfile.setDescription("Thermostat device profile"); + thermostatDeviceProfile.setDefaultRuleChainId(ruleChainService.findTenantRuleChains( + demoTenant.getId(), new PageLink(1, 0, "Thermostat")).getData().get(0).getId()); + + DeviceProfileData deviceProfileData = new DeviceProfileData(); + DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); + DefaultDeviceProfileTransportConfiguration transportConfiguration = new DefaultDeviceProfileTransportConfiguration(); + DisabledDeviceProfileProvisionConfiguration provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(null); + deviceProfileData.setConfiguration(configuration); + deviceProfileData.setTransportConfiguration(transportConfiguration); + deviceProfileData.setProvisionConfiguration(provisionConfiguration); + thermostatDeviceProfile.setProfileData(deviceProfileData); + + DeviceProfileAlarm highTemperature = new DeviceProfileAlarm(); + highTemperature.setId("highTemperatureAlarmID"); + highTemperature.setAlarmType("High Temperature"); + AlarmRule temperatureRule = new AlarmRule(); + AlarmCondition temperatureCondition = new AlarmCondition(); + temperatureCondition.setSpec(new SimpleAlarmConditionSpec()); + + KeyFilter temperatureAlarmFlagAttributeFilter = new KeyFilter(); + temperatureAlarmFlagAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperatureAlarmFlag")); + temperatureAlarmFlagAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN); + BooleanFilterPredicate temperatureAlarmFlagAttributePredicate = new BooleanFilterPredicate(); + temperatureAlarmFlagAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL); + temperatureAlarmFlagAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE)); + temperatureAlarmFlagAttributeFilter.setPredicate(temperatureAlarmFlagAttributePredicate); + + KeyFilter temperatureTimeseriesFilter = new KeyFilter(); + temperatureTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); + temperatureTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate temperatureTimeseriesFilterPredicate = new NumericFilterPredicate(); + temperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); + FilterPredicateValue temperatureTimeseriesPredicateValue = + new FilterPredicateValue<>(25.0, null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "temperatureAlarmThreshold")); + temperatureTimeseriesFilterPredicate.setValue(temperatureTimeseriesPredicateValue); + temperatureTimeseriesFilter.setPredicate(temperatureTimeseriesFilterPredicate); + temperatureCondition.setCondition(Arrays.asList(temperatureAlarmFlagAttributeFilter, temperatureTimeseriesFilter)); + temperatureRule.setAlarmDetails("Current temperature = ${temperature}"); + temperatureRule.setCondition(temperatureCondition); + highTemperature.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.MAJOR, temperatureRule))); + + AlarmRule clearTemperatureRule = new AlarmRule(); + AlarmCondition clearTemperatureCondition = new AlarmCondition(); + clearTemperatureCondition.setSpec(new SimpleAlarmConditionSpec()); + + KeyFilter clearTemperatureTimeseriesFilter = new KeyFilter(); + clearTemperatureTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); + clearTemperatureTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate clearTemperatureTimeseriesFilterPredicate = new NumericFilterPredicate(); + clearTemperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS_OR_EQUAL); + FilterPredicateValue clearTemperatureTimeseriesPredicateValue = + new FilterPredicateValue<>(25.0, null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "temperatureAlarmThreshold")); + + clearTemperatureTimeseriesFilterPredicate.setValue(clearTemperatureTimeseriesPredicateValue); + clearTemperatureTimeseriesFilter.setPredicate(clearTemperatureTimeseriesFilterPredicate); + clearTemperatureCondition.setCondition(Collections.singletonList(clearTemperatureTimeseriesFilter)); + clearTemperatureRule.setCondition(clearTemperatureCondition); + clearTemperatureRule.setAlarmDetails("Current temperature = ${temperature}"); + highTemperature.setClearRule(clearTemperatureRule); + + DeviceProfileAlarm lowHumidity = new DeviceProfileAlarm(); + lowHumidity.setId("lowHumidityAlarmID"); + lowHumidity.setAlarmType("Low Humidity"); + AlarmRule humidityRule = new AlarmRule(); + AlarmCondition humidityCondition = new AlarmCondition(); + humidityCondition.setSpec(new SimpleAlarmConditionSpec()); + + KeyFilter humidityAlarmFlagAttributeFilter = new KeyFilter(); + humidityAlarmFlagAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "humidityAlarmFlag")); + humidityAlarmFlagAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN); + BooleanFilterPredicate humidityAlarmFlagAttributePredicate = new BooleanFilterPredicate(); + humidityAlarmFlagAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL); + humidityAlarmFlagAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE)); + humidityAlarmFlagAttributeFilter.setPredicate(humidityAlarmFlagAttributePredicate); + + KeyFilter humidityTimeseriesFilter = new KeyFilter(); + humidityTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "humidity")); + humidityTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate humidityTimeseriesFilterPredicate = new NumericFilterPredicate(); + humidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS); + FilterPredicateValue humidityTimeseriesPredicateValue = + new FilterPredicateValue<>(60.0, null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "humidityAlarmThreshold")); + humidityTimeseriesFilterPredicate.setValue(humidityTimeseriesPredicateValue); + humidityTimeseriesFilter.setPredicate(humidityTimeseriesFilterPredicate); + humidityCondition.setCondition(Arrays.asList(humidityAlarmFlagAttributeFilter, humidityTimeseriesFilter)); + + humidityRule.setCondition(humidityCondition); + humidityRule.setAlarmDetails("Current humidity = ${humidity}"); + lowHumidity.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.MINOR, humidityRule))); + + AlarmRule clearHumidityRule = new AlarmRule(); + AlarmCondition clearHumidityCondition = new AlarmCondition(); + clearHumidityCondition.setSpec(new SimpleAlarmConditionSpec()); + + KeyFilter clearHumidityTimeseriesFilter = new KeyFilter(); + clearHumidityTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "humidity")); + clearHumidityTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate clearHumidityTimeseriesFilterPredicate = new NumericFilterPredicate(); + clearHumidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER_OR_EQUAL); + FilterPredicateValue clearHumidityTimeseriesPredicateValue = + new FilterPredicateValue<>(60.0, null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "humidityAlarmThreshold")); + + clearHumidityTimeseriesFilterPredicate.setValue(clearHumidityTimeseriesPredicateValue); + clearHumidityTimeseriesFilter.setPredicate(clearHumidityTimeseriesFilterPredicate); + clearHumidityCondition.setCondition(Collections.singletonList(clearHumidityTimeseriesFilter)); + clearHumidityRule.setCondition(clearHumidityCondition); + clearHumidityRule.setAlarmDetails("Current humidity = ${humidity}"); + lowHumidity.setClearRule(clearHumidityRule); + + deviceProfileData.setAlarms(Arrays.asList(highTemperature, lowHumidity)); + + DeviceProfile savedThermostatDeviceProfile = deviceProfileService.saveDeviceProfile(thermostatDeviceProfile); + + DeviceId t1Id = createDevice(demoTenant.getId(), null, savedThermostatDeviceProfile.getId(), "Thermostat T1", "T1_TEST_TOKEN", "Demo device for Thermostats dashboard").getId(); + DeviceId t2Id = createDevice(demoTenant.getId(), null, savedThermostatDeviceProfile.getId(), "Thermostat T2", "T2_TEST_TOKEN", "Demo device for Thermostats dashboard").getId(); attributesService.save(demoTenant.getId(), t1Id, DataConstants.SERVER_SCOPE, Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.3948)), new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", -122.1503)), - new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmTemperature", true)), - new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmHumidity", true)), - new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdTemperature", (long) 20)), - new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdHumidity", (long) 50)))); + new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("temperatureAlarmFlag", true)), + new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("humidityAlarmFlag", true)), + new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("temperatureAlarmThreshold", (long) 20)), + new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("humidityAlarmThreshold", (long) 50)))); attributesService.save(demoTenant.getId(), t2Id, DataConstants.SERVER_SCOPE, Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.493801)), new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", -121.948769)), - new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmTemperature", true)), - new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmHumidity", true)), - new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdTemperature", (long) 25)), - new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdHumidity", (long) 30)))); + new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("temperatureAlarmFlag", true)), + new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("humidityAlarmFlag", true)), + new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("temperatureAlarmThreshold", (long) 25)), + new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("humidityAlarmThreshold", (long) 30)))); installScripts.loadDashboards(demoTenant.getId(), null); } 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 ac047060ae..5e6797eeb7 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 @@ -210,26 +210,9 @@ public class InstallScripts { public void loadDemoRuleChains(TenantId tenantId) throws Exception { - Path ruleChainsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, RULE_CHAINS_DIR); try { - JsonNode ruleChainJson = objectMapper.readTree(ruleChainsDir.resolve("thermostat_alarms.json").toFile()); - RuleChain ruleChain = objectMapper.treeToValue(ruleChainJson.get("ruleChain"), RuleChain.class); - RuleChainMetaData ruleChainMetaData = objectMapper.treeToValue(ruleChainJson.get("metadata"), RuleChainMetaData.class); - ruleChain.setTenantId(tenantId); - ruleChain = ruleChainService.saveRuleChain(ruleChain); - ruleChainMetaData.setRuleChainId(ruleChain.getId()); - ruleChainService.saveRuleChainMetaData(new TenantId(EntityId.NULL_UUID), ruleChainMetaData); - - JsonNode rootChainJson = objectMapper.readTree(ruleChainsDir.resolve("root_rule_chain.json").toFile()); - RuleChain rootChain = objectMapper.treeToValue(rootChainJson.get("ruleChain"), RuleChain.class); - RuleChainMetaData rootChainMetaData = objectMapper.treeToValue(rootChainJson.get("metadata"), RuleChainMetaData.class); - - RuleChainId thermostatsRuleChainId = ruleChain.getId(); - rootChainMetaData.getRuleChainConnections().forEach(connection -> connection.setTargetRuleChainId(thermostatsRuleChainId)); - rootChain.setTenantId(tenantId); - rootChain = ruleChainService.saveRuleChain(rootChain); - rootChainMetaData.setRuleChainId(rootChain.getId()); - ruleChainService.saveRuleChainMetaData(new TenantId(EntityId.NULL_UUID), rootChainMetaData); + createDefaultRuleChains(tenantId); + createDefaultRuleChain(tenantId, "Thermostat"); } catch (Exception e) { log.error("Unable to load dashboard from json", e); throw new RuntimeException("Unable to load dashboard from json", e); diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index 19727684a3..b0aabaea08 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -367,6 +367,8 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService " db_storage varchar(32)," + " re_exec varchar(32)," + " js_exec varchar(32)," + + " email_exec varchar(32)," + + " sms_exec varchar(32)," + " CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id)\n" + ");"); } catch (Exception e) { @@ -419,6 +421,19 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService log.error("Failed updating schema!!!", e); } break; + case "3.2.0": + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { + log.info("Updating schema ..."); + try { + conn.createStatement().execute("CREATE INDEX IF NOT EXISTS idx_device_device_profile_id ON device(tenant_id, device_profile_id);"); + conn.createStatement().execute("ALTER TABLE dashboard ALTER COLUMN configuration TYPE varchar;"); + conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3002001;"); + } catch (Exception e) { + log.error("Failed updating schema!!!", e); + } + log.info("Schema updated."); + } + break; default: throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); } diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index bcf541880c..e9cef05dd5 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -20,8 +20,10 @@ import freemarker.template.Configuration; import freemarker.template.Template; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Lazy; import org.springframework.core.NestedRuntimeException; import org.springframework.mail.javamail.JavaMailSenderImpl; import org.springframework.mail.javamail.MimeMessageHelper; @@ -29,12 +31,18 @@ import org.springframework.stereotype.Service; import org.springframework.ui.freemarker.FreeMarkerTemplateUtils; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.server.common.data.AdminSettings; +import org.thingsboard.server.common.data.ApiFeature; +import org.thingsboard.server.common.data.ApiUsageRecordKey; +import org.thingsboard.server.common.data.ApiUsageStateMailMessage; +import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.settings.AdminSettingsService; +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import javax.annotation.PostConstruct; import javax.mail.MessagingException; @@ -51,18 +59,28 @@ public class DefaultMailService implements MailService { public static final String MAIL_PROP = "mail."; public static final String TARGET_EMAIL = "targetEmail"; public static final String UTF_8 = "UTF-8"; - @Autowired - private MessageSource messages; + public static final int _10K = 10000; + public static final int _1M = 1000000; + + private final MessageSource messages; + private final Configuration freemarkerConfig; + private final AdminSettingsService adminSettingsService; + private final TbApiUsageClient apiUsageClient; + @Lazy @Autowired - private Configuration freemarkerConfig; + private TbApiUsageStateService apiUsageStateService; private JavaMailSenderImpl mailSender; private String mailFrom; - @Autowired - private AdminSettingsService adminSettingsService; + public DefaultMailService(MessageSource messages, Configuration freemarkerConfig, AdminSettingsService adminSettingsService, TbApiUsageClient apiUsageClient) { + this.messages = messages; + this.freemarkerConfig = freemarkerConfig; + this.adminSettingsService = adminSettingsService; + this.apiUsageClient = apiUsageClient; + } @PostConstruct private void init() { @@ -141,7 +159,7 @@ public class DefaultMailService implements MailService { } @Override - public void sendEmail(String email, String subject, String message) throws ThingsboardException { + public void sendEmail(TenantId tenantId, String email, String subject, String message) throws ThingsboardException { sendMail(mailSender, mailFrom, email, subject, message); } @@ -216,20 +234,25 @@ public class DefaultMailService implements MailService { } @Override - public void send(String from, String to, String cc, String bcc, String subject, String body) throws MessagingException { - MimeMessage mailMsg = mailSender.createMimeMessage(); - MimeMessageHelper helper = new MimeMessageHelper(mailMsg, "UTF-8"); - helper.setFrom(StringUtils.isBlank(from) ? mailFrom : from); - helper.setTo(to.split("\\s*,\\s*")); - if (!StringUtils.isBlank(cc)) { - helper.setCc(cc.split("\\s*,\\s*")); - } - if (!StringUtils.isBlank(bcc)) { - helper.setBcc(bcc.split("\\s*,\\s*")); + public void send(TenantId tenantId, String from, String to, String cc, String bcc, String subject, String body) throws MessagingException { + if (apiUsageStateService.getApiUsageState(tenantId).isEmailSendEnabled()) { + MimeMessage mailMsg = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(mailMsg, "UTF-8"); + helper.setFrom(StringUtils.isBlank(from) ? mailFrom : from); + helper.setTo(to.split("\\s*,\\s*")); + if (!StringUtils.isBlank(cc)) { + helper.setCc(cc.split("\\s*,\\s*")); + } + if (!StringUtils.isBlank(bcc)) { + helper.setBcc(bcc.split("\\s*,\\s*")); + } + helper.setSubject(subject); + helper.setText(body); + mailSender.send(helper.getMimeMessage()); + apiUsageClient.report(tenantId, ApiUsageRecordKey.EMAIL_EXEC_COUNT, 1); + } else { + throw new RuntimeException("Email sending is disabled due to API limits!"); } - helper.setSubject(subject); - helper.setText(body); - mailSender.send(helper.getMimeMessage()); } @Override @@ -246,6 +269,122 @@ public class DefaultMailService implements MailService { sendMail(mailSender, mailFrom, email, subject, message); } + @Override + public void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage msg) throws ThingsboardException { + String subject = messages.getMessage("api.usage.state", null, Locale.US); + + Map model = new HashMap<>(); + model.put("apiFeature", apiFeature.getLabel()); + model.put(TARGET_EMAIL, email); + + String message = null; + + switch (stateValue) { + case ENABLED: + model.put("apiLabel", toEnabledValueLabel(apiFeature)); + message = mergeTemplateIntoString("state.enabled.ftl", model); + break; + case WARNING: + model.put("apiValueLabel", toDisabledValueLabel(apiFeature) + " " + toWarningValueLabel(msg.getKey(), msg.getValue(), msg.getThreshold())); + message = mergeTemplateIntoString("state.warning.ftl", model); + break; + case DISABLED: + model.put("apiLimitValueLabel", toDisabledValueLabel(apiFeature) + " " + toDisabledValueLabel(msg.getKey(), msg.getThreshold())); + message = mergeTemplateIntoString("state.disabled.ftl", model); + break; + } + sendMail(mailSender, mailFrom, email, subject, message); + } + + private String toEnabledValueLabel(ApiFeature apiFeature) { + switch (apiFeature) { + case DB: + return "save"; + case TRANSPORT: + return "receive"; + case JS: + return "invoke"; + case RE: + return "process"; + case EMAIL: + case SMS: + return "send"; + default: + throw new RuntimeException("Not implemented!"); + } + } + + private String toDisabledValueLabel(ApiFeature apiFeature) { + switch (apiFeature) { + case DB: + return "saved"; + case TRANSPORT: + return "received"; + case JS: + return "invoked"; + case RE: + return "processed"; + case EMAIL: + case SMS: + return "sent"; + default: + throw new RuntimeException("Not implemented!"); + } + } + + private String toWarningValueLabel(ApiUsageRecordKey key, long value, long threshold) { + String valueInM = getValueAsString(value); + String thresholdInM = getValueAsString(threshold); + switch (key) { + case STORAGE_DP_COUNT: + case TRANSPORT_DP_COUNT: + return valueInM + " out of " + thresholdInM + " allowed data points"; + case TRANSPORT_MSG_COUNT: + return valueInM + " out of " + thresholdInM + " allowed messages"; + case JS_EXEC_COUNT: + return valueInM + " out of " + thresholdInM + " allowed JavaScript functions"; + case RE_EXEC_COUNT: + return valueInM + " out of " + thresholdInM + " allowed Rule Engine messages"; + case EMAIL_EXEC_COUNT: + return valueInM + " out of " + thresholdInM + " allowed Email messages"; + case SMS_EXEC_COUNT: + return valueInM + " out of " + thresholdInM + " allowed SMS messages"; + default: + throw new RuntimeException("Not implemented!"); + } + } + + private String toDisabledValueLabel(ApiUsageRecordKey key, long value) { + switch (key) { + case STORAGE_DP_COUNT: + case TRANSPORT_DP_COUNT: + return getValueAsString(value) + " data points"; + case TRANSPORT_MSG_COUNT: + return getValueAsString(value) + " messages"; + case JS_EXEC_COUNT: + return "JavaScript functions " + getValueAsString(value) + " times"; + case RE_EXEC_COUNT: + return getValueAsString(value) + " Rule Engine messages"; + case EMAIL_EXEC_COUNT: + return getValueAsString(value) + " Email messages"; + case SMS_EXEC_COUNT: + return getValueAsString(value) + " SMS messages"; + default: + throw new RuntimeException("Not implemented!"); + } + } + + @NotNull + private String getValueAsString(long value) { + if (value > _1M && value % _1M < _10K) { + return value / _1M + "M"; + } else if (value > _10K) { + return String.format("%.2fM", ((double) value) / 1000000); + } else { + return value + ""; + } + } + private void sendMail(JavaMailSenderImpl mailSender, String mailFrom, String email, String subject, String message) throws ThingsboardException { diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index edd53c6727..a8a29f09e0 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -22,6 +22,7 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.HasName; @@ -32,7 +33,6 @@ import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; @@ -237,6 +237,16 @@ public class DefaultTbClusterService implements TbClusterService { onEntityDelete(TenantId.SYS_TENANT_ID, entity.getId(), entity.getName(), callback); } + @Override + public void onDeviceChange(Device entity, TbQueueCallback callback) { + onEntityChange(entity.getTenantId(), entity.getId(), entity, callback); + } + + @Override + public void onDeviceDeleted(Device entity, TbQueueCallback callback) { + onEntityDelete(entity.getTenantId(), entity.getId(), entity.getName(), callback); + } + public void onEntityChange(TenantId tenantId, EntityId entityid, T entity, TbQueueCallback callback) { String entityName = (entity instanceof HasName) ? ((HasName) entity).getName() : entity.getClass().getName(); log.trace("[{}][{}][{}] Processing [{}] change event", tenantId, entityid.getEntityType(), entityid.getId(), entityName); diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index f56e08b10f..b9d9f57b92 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.service.queue; +import lombok.Getter; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.event.ApplicationReadyEvent; @@ -76,6 +78,7 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.stream.Collectors; @@ -175,39 +178,48 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService> ctx = new TbPackProcessingContext<>( processingTimeoutLatch, pendingMap, new ConcurrentHashMap<>()); - pendingMap.forEach((id, msg) -> { - log.trace("[{}] Creating main callback for message: {}", id, msg.getValue()); - TbCallback callback = new TbPackCallback<>(id, ctx); - try { - ToCoreMsg toCoreMsg = msg.getValue(); - if (toCoreMsg.hasToSubscriptionMgrMsg()) { - log.trace("[{}] Forwarding message to subscription manager service {}", id, toCoreMsg.getToSubscriptionMgrMsg()); - forwardToSubMgrService(toCoreMsg.getToSubscriptionMgrMsg(), callback); - } else if (toCoreMsg.hasToDeviceActorMsg()) { - log.trace("[{}] Forwarding message to device actor {}", id, toCoreMsg.getToDeviceActorMsg()); - forwardToDeviceActor(toCoreMsg.getToDeviceActorMsg(), callback); - } else if (toCoreMsg.hasDeviceStateServiceMsg()) { - log.trace("[{}] Forwarding message to state service {}", id, toCoreMsg.getDeviceStateServiceMsg()); - forwardToStateService(toCoreMsg.getDeviceStateServiceMsg(), callback); - } else if (toCoreMsg.getToDeviceActorNotificationMsg() != null && !toCoreMsg.getToDeviceActorNotificationMsg().isEmpty()) { - Optional actorMsg = encodingService.decode(toCoreMsg.getToDeviceActorNotificationMsg().toByteArray()); - if (actorMsg.isPresent()) { - TbActorMsg tbActorMsg = actorMsg.get(); - if (tbActorMsg.getMsgType().equals(MsgType.DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG)) { - tbCoreDeviceRpcService.forwardRpcRequestToDeviceActor((ToDeviceRpcRequestActorMsg) tbActorMsg); - } else { - log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg.get()); - actorContext.tell(actorMsg.get()); + PendingMsgHolder pendingMsgHolder = new PendingMsgHolder(); + Future packSubmitFuture = consumersExecutor.submit(() -> { + pendingMap.forEach((id, msg) -> { + log.trace("[{}] Creating main callback for message: {}", id, msg.getValue()); + TbCallback callback = new TbPackCallback<>(id, ctx); + try { + ToCoreMsg toCoreMsg = msg.getValue(); + pendingMsgHolder.setToCoreMsg(toCoreMsg); + if (toCoreMsg.hasToSubscriptionMgrMsg()) { + log.trace("[{}] Forwarding message to subscription manager service {}", id, toCoreMsg.getToSubscriptionMgrMsg()); + forwardToSubMgrService(toCoreMsg.getToSubscriptionMgrMsg(), callback); + } else if (toCoreMsg.hasToDeviceActorMsg()) { + log.trace("[{}] Forwarding message to device actor {}", id, toCoreMsg.getToDeviceActorMsg()); + forwardToDeviceActor(toCoreMsg.getToDeviceActorMsg(), callback); + } else if (toCoreMsg.hasDeviceStateServiceMsg()) { + log.trace("[{}] Forwarding message to state service {}", id, toCoreMsg.getDeviceStateServiceMsg()); + forwardToStateService(toCoreMsg.getDeviceStateServiceMsg(), callback); + } else if (toCoreMsg.getToDeviceActorNotificationMsg() != null && !toCoreMsg.getToDeviceActorNotificationMsg().isEmpty()) { + Optional actorMsg = encodingService.decode(toCoreMsg.getToDeviceActorNotificationMsg().toByteArray()); + if (actorMsg.isPresent()) { + TbActorMsg tbActorMsg = actorMsg.get(); + if (tbActorMsg.getMsgType().equals(MsgType.DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG)) { + tbCoreDeviceRpcService.forwardRpcRequestToDeviceActor((ToDeviceRpcRequestActorMsg) tbActorMsg); + } else { + log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg.get()); + actorContext.tell(actorMsg.get()); + } } + callback.onSuccess(); } - callback.onSuccess(); + } catch (Throwable e) { + log.warn("[{}] Failed to process message: {}", id, msg, e); + callback.onFailure(e); } - } catch (Throwable e) { - log.warn("[{}] Failed to process message: {}", id, msg, e); - callback.onFailure(e); - } + }); }); if (!processingTimeoutLatch.await(packProcessingTimeout, TimeUnit.MILLISECONDS)) { + if (!packSubmitFuture.isDone()) { + packSubmitFuture.cancel(true); + ToCoreMsg lastSubmitMsg = pendingMsgHolder.getToCoreMsg(); + log.info("Timeout to process message: {}", lastSubmitMsg); + } ctx.getAckMap().forEach((id, msg) -> log.debug("[{}] Timeout to process message: {}", id, msg.getValue())); ctx.getFailedMap().forEach((id, msg) -> log.warn("[{}] Failed to process message: {}", id, msg.getValue())); } @@ -227,6 +239,12 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService MAX_SMS_MESSAGE_LENGTH) { + log.warn("SMS message exceeds maximum symbols length and will be truncated"); + message = message.substring(0, MAX_SMS_MESSAGE_LENGTH); + } + return message; + } + + protected int countMessageSegments(String message) { + return (int)Math.ceil((double) message.length() / (double) MAX_SMS_SEGMENT_LENGTH); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java new file mode 100644 index 0000000000..d319276c40 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2020 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.sms; + +import org.springframework.stereotype.Component; +import org.thingsboard.rule.engine.api.sms.SmsSender; +import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; +import org.thingsboard.server.common.data.sms.config.AwsSnsSmsProviderConfiguration; +import org.thingsboard.server.common.data.sms.config.SmsProviderConfiguration; +import org.thingsboard.server.common.data.sms.config.TwilioSmsProviderConfiguration; +import org.thingsboard.server.service.sms.aws.AwsSmsSender; +import org.thingsboard.server.service.sms.twilio.TwilioSmsSender; + +@Component +public class DefaultSmsSenderFactory implements SmsSenderFactory { + + @Override + public SmsSender createSmsSender(SmsProviderConfiguration config) { + switch (config.getType()) { + case AWS_SNS: + return new AwsSmsSender((AwsSnsSmsProviderConfiguration)config); + case TWILIO: + return new TwilioSmsSender((TwilioSmsProviderConfiguration)config); + default: + throw new RuntimeException("Unknown SMS provider type " + config.getType()); + } + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java new file mode 100644 index 0000000000..2a8b2ef913 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java @@ -0,0 +1,145 @@ +/** + * Copyright © 2016-2020 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.sms; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.NestedRuntimeException; +import org.springframework.stereotype.Service; +import org.thingsboard.rule.engine.api.SmsService; +import org.thingsboard.rule.engine.api.sms.SmsSender; +import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; +import org.thingsboard.server.common.data.sms.config.SmsProviderConfiguration; +import org.thingsboard.server.common.data.sms.config.TestSmsRequest; +import org.thingsboard.server.common.data.AdminSettings; +import org.thingsboard.server.common.data.ApiUsageRecordKey; +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.settings.AdminSettingsService; +import org.thingsboard.server.dao.util.mapping.JacksonUtil; +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +@Service +@Slf4j +public class DefaultSmsService implements SmsService { + + private final SmsSenderFactory smsSenderFactory; + private final AdminSettingsService adminSettingsService; + private final TbApiUsageStateService apiUsageStateService; + private final TbApiUsageClient apiUsageClient; + + private SmsSender smsSender; + + public DefaultSmsService(SmsSenderFactory smsSenderFactory, AdminSettingsService adminSettingsService, TbApiUsageStateService apiUsageStateService, TbApiUsageClient apiUsageClient) { + this.smsSenderFactory = smsSenderFactory; + this.adminSettingsService = adminSettingsService; + this.apiUsageStateService = apiUsageStateService; + this.apiUsageClient = apiUsageClient; + } + + @PostConstruct + private void init() { + updateSmsConfiguration(); + } + + @PreDestroy + private void destroy() { + if (this.smsSender != null) { + this.smsSender.destroy(); + } + } + + @Override + public void updateSmsConfiguration() { + AdminSettings settings = adminSettingsService.findAdminSettingsByKey(new TenantId(EntityId.NULL_UUID), "sms"); + if (settings != null) { + try { + JsonNode jsonConfig = settings.getJsonValue(); + SmsProviderConfiguration configuration = JacksonUtil.convertValue(jsonConfig, SmsProviderConfiguration.class); + SmsSender newSmsSender = this.smsSenderFactory.createSmsSender(configuration); + if (this.smsSender != null) { + this.smsSender.destroy(); + } + this.smsSender = newSmsSender; + } catch (Exception e) { + log.error("Failed to create SMS sender", e); + } + } + } + + private int sendSms(String numberTo, String message) throws ThingsboardException { + if (this.smsSender == null) { + throw new ThingsboardException("Unable to send SMS: no SMS provider configured!", ThingsboardErrorCode.GENERAL); + } + return this.sendSms(this.smsSender, numberTo, message); + } + + @Override + public void sendSms(TenantId tenantId, String[] numbersTo, String message) throws ThingsboardException { + if (apiUsageStateService.getApiUsageState(tenantId).isSmsSendEnabled()) { + int smsCount = 0; + try { + for (String numberTo : numbersTo) { + smsCount += this.sendSms(numberTo, message); + } + } finally { + if (smsCount > 0) { + apiUsageClient.report(tenantId, ApiUsageRecordKey.SMS_EXEC_COUNT, smsCount); + } + } + } else { + throw new RuntimeException("SMS sending is disabled due to API limits!"); + } + } + + @Override + public void sendTestSms(TestSmsRequest testSmsRequest) throws ThingsboardException { + SmsSender testSmsSender; + try { + testSmsSender = this.smsSenderFactory.createSmsSender(testSmsRequest.getProviderConfiguration()); + } catch (Exception e) { + throw handleException(e); + } + this.sendSms(testSmsSender, testSmsRequest.getNumberTo(), testSmsRequest.getMessage()); + testSmsSender.destroy(); + } + + private int sendSms(SmsSender smsSender, String numberTo, String message) throws ThingsboardException { + try { + return smsSender.sendSms(numberTo, message); + } catch (Exception e) { + throw handleException(e); + } + } + + private ThingsboardException handleException(Exception exception) { + String message; + if (exception instanceof NestedRuntimeException) { + message = ((NestedRuntimeException) exception).getMostSpecificCause().getMessage(); + } else { + message = exception.getMessage(); + } + log.warn("Unable to send SMS: {}", message); + return new ThingsboardException(String.format("Unable to send SMS: %s", message), + ThingsboardErrorCode.GENERAL); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/sms/SmsExecutorService.java b/application/src/main/java/org/thingsboard/server/service/sms/SmsExecutorService.java new file mode 100644 index 0000000000..48aa2d5dc1 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sms/SmsExecutorService.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016-2020 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.sms; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.thingsboard.common.util.AbstractListeningExecutor; + +@Component +public class SmsExecutorService extends AbstractListeningExecutor { + + @Value("${actors.rule.sms_thread_pool_size}") + private int smsExecutorThreadPoolSize; + + @Override + protected int getThreadPollSize() { + return smsExecutorThreadPoolSize; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sms/aws/AwsSmsSender.java b/application/src/main/java/org/thingsboard/server/service/sms/aws/AwsSmsSender.java new file mode 100644 index 0000000000..47b677cb14 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sms/aws/AwsSmsSender.java @@ -0,0 +1,86 @@ +/** + * Copyright © 2016-2020 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.sms.aws; + +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.sns.AmazonSNS; +import com.amazonaws.services.sns.AmazonSNSClient; +import com.amazonaws.services.sns.model.MessageAttributeValue; +import com.amazonaws.services.sns.model.PublishRequest; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.thingsboard.server.common.data.sms.config.AwsSnsSmsProviderConfiguration; +import org.thingsboard.rule.engine.api.sms.exception.SmsException; +import org.thingsboard.rule.engine.api.sms.exception.SmsSendException; +import org.thingsboard.server.service.sms.AbstractSmsSender; + +import java.util.HashMap; +import java.util.Map; + +@Slf4j +public class AwsSmsSender extends AbstractSmsSender { + + private static final Map SMS_ATTRIBUTES = new HashMap<>(); + + static { + SMS_ATTRIBUTES.put("AWS.SNS.SMS.SMSType", new MessageAttributeValue() + .withStringValue("Transactional") + .withDataType("String")); + } + + private AmazonSNS snsClient; + + public AwsSmsSender(AwsSnsSmsProviderConfiguration config) { + if (StringUtils.isEmpty(config.getAccessKeyId()) || StringUtils.isEmpty(config.getSecretAccessKey()) || StringUtils.isEmpty(config.getRegion())) { + throw new IllegalArgumentException("Invalid AWS sms provider configuration: aws accessKeyId, aws secretAccessKey and aws region should be specified!"); + } + AWSCredentials awsCredentials = new BasicAWSCredentials(config.getAccessKeyId(), config.getSecretAccessKey()); + AWSStaticCredentialsProvider credProvider = new AWSStaticCredentialsProvider(awsCredentials); + this.snsClient = AmazonSNSClient.builder() + .withCredentials(credProvider) + .withRegion(config.getRegion()) + .build(); + } + + @Override + public int sendSms(String numberTo, String message) throws SmsException { + numberTo = this.validatePhoneNumber(numberTo); + message = this.prepareMessage(message); + try { + PublishRequest publishRequest = new PublishRequest() + .withMessageAttributes(SMS_ATTRIBUTES) + .withPhoneNumber(numberTo) + .withMessage(message); + this.snsClient.publish(publishRequest); + return this.countMessageSegments(message); + } catch (Exception e) { + throw new SmsSendException("Failed to send SMS message - " + e.getMessage(), e); + } + } + + @Override + public void destroy() { + if (this.snsClient != null) { + try { + this.snsClient.shutdown(); + } catch (Exception e) { + log.error("Failed to shutdown SNS client during destroy()", e); + } + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/sms/twilio/TwilioSmsSender.java b/application/src/main/java/org/thingsboard/server/service/sms/twilio/TwilioSmsSender.java new file mode 100644 index 0000000000..7af65cfc48 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sms/twilio/TwilioSmsSender.java @@ -0,0 +1,56 @@ +/** + * Copyright © 2016-2020 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.sms.twilio; + +import com.twilio.http.TwilioRestClient; +import com.twilio.rest.api.v2010.account.Message; +import com.twilio.type.PhoneNumber; +import org.apache.commons.lang3.StringUtils; +import org.thingsboard.server.common.data.sms.config.TwilioSmsProviderConfiguration; +import org.thingsboard.rule.engine.api.sms.exception.SmsException; +import org.thingsboard.rule.engine.api.sms.exception.SmsSendException; +import org.thingsboard.server.service.sms.AbstractSmsSender; + +public class TwilioSmsSender extends AbstractSmsSender { + + private TwilioRestClient twilioRestClient; + private String numberFrom; + + public TwilioSmsSender(TwilioSmsProviderConfiguration config) { + if (StringUtils.isEmpty(config.getAccountSid()) || StringUtils.isEmpty(config.getAccountToken()) || StringUtils.isEmpty(config.getNumberFrom())) { + throw new IllegalArgumentException("Invalid twilio sms provider configuration: accountSid, accountToken and numberFrom should be specified!"); + } + this.numberFrom = this.validatePhoneNumber(config.getNumberFrom()); + this.twilioRestClient = new TwilioRestClient.Builder(config.getAccountSid(), config.getAccountToken()).build(); + } + + @Override + public int sendSms(String numberTo, String message) throws SmsException { + numberTo = this.validatePhoneNumber(numberTo); + message = this.prepareMessage(message); + try { + String numSegments = Message.creator(new PhoneNumber(numberTo), new PhoneNumber(this.numberFrom), message).create(this.twilioRestClient).getNumSegments(); + return Integer.valueOf(numSegments); + } catch (Exception e) { + throw new SmsSendException("Failed to send SMS message - " + e.getMessage(), e); + } + } + + @Override + public void destroy() { + + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 5ef5adc386..234b64f94e 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -521,27 +521,27 @@ public class DefaultDeviceStateService implements DeviceStateService { private void save(DeviceId deviceId, String key, long value) { if (persistToTelemetry) { - tsSubService.saveAndNotify( + tsSubService.saveAndNotifyInternal( TenantId.SYS_TENANT_ID, deviceId, Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry(key, value))), - new AttributeSaveCallback(deviceId, key, value)); + new AttributeSaveCallback<>(deviceId, key, value)); } else { - tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback(deviceId, key, value)); + tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback<>(deviceId, key, value)); } } private void save(DeviceId deviceId, String key, boolean value) { if (persistToTelemetry) { - tsSubService.saveAndNotify( + tsSubService.saveAndNotifyInternal( TenantId.SYS_TENANT_ID, deviceId, Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry(key, value))), - new AttributeSaveCallback(deviceId, key, value)); + new AttributeSaveCallback<>(deviceId, key, value)); } else { - tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback(deviceId, key, value)); + tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback<>(deviceId, key, value)); } } - private static class AttributeSaveCallback implements FutureCallback { + private static class AttributeSaveCallback implements FutureCallback { private final DeviceId deviceId; private final String key; private final Object value; @@ -553,7 +553,7 @@ public class DefaultDeviceStateService implements DeviceStateService { } @Override - public void onSuccess(@Nullable Void result) { + public void onSuccess(@Nullable T result) { log.trace("[{}] Successfully updated attribute [{}] with value [{}]", deviceId, key, value); } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java index 4e62628851..cd85c44cbe 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java @@ -21,12 +21,6 @@ import org.springframework.context.annotation.Lazy; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.EntityView; -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.dao.entityview.EntityViewService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.ClusterTopologyChangeEvent; import org.thingsboard.server.queue.discovery.PartitionChangeEvent; @@ -36,7 +30,6 @@ import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.queue.TbClusterService; -import org.thingsboard.server.service.telemetry.DefaultTelemetryWebSocketService; import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; @@ -49,7 +42,6 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.stream.Collectors; @Slf4j @TbCoreComponent @@ -59,9 +51,6 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer private final Set currentPartitions = ConcurrentHashMap.newKeySet(); private final Map> subscriptionsBySessionId = new ConcurrentHashMap<>(); - @Autowired - private EntityViewService entityViewService; - @Autowired private PartitionService partitionService; @@ -72,17 +61,17 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer @Lazy private SubscriptionManagerService subscriptionManagerService; - private ExecutorService wsCallBackExecutor; + private ExecutorService subscriptionUpdateExecutor; @PostConstruct public void initExecutor() { - wsCallBackExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("ws-sub-callback")); + subscriptionUpdateExecutor = Executors.newWorkStealingPool(20); } @PreDestroy public void shutdownExecutor() { - if (wsCallBackExecutor != null) { - wsCallBackExecutor.shutdownNow(); + if (subscriptionUpdateExecutor != null) { + subscriptionUpdateExecutor.shutdownNow(); } } @@ -148,7 +137,7 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer update.getLatestValues().forEach((key, value) -> attrSub.getKeyStates().put(key, value)); break; } - subscription.getUpdateConsumer().accept(sessionId, update); + subscriptionUpdateExecutor.submit(() -> subscription.getUpdateConsumer().accept(sessionId, update)); } callback.onSuccess(); } @@ -158,7 +147,7 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer TbSubscription subscription = subscriptionsBySessionId .getOrDefault(sessionId, Collections.emptyMap()).get(update.getSubscriptionId()); if (subscription != null && subscription.getType() == TbSubscriptionType.ALARMS) { - subscription.getUpdateConsumer().accept(sessionId, update); + subscriptionUpdateExecutor.submit(() -> subscription.getUpdateConsumer().accept(sessionId, update)); } callback.onSuccess(); } 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 7a35e6b317..5b3ad05ee9 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 @@ -25,6 +25,7 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; @@ -34,13 +35,14 @@ import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; 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.dao.attributes.AttributesService; import org.thingsboard.server.dao.entityview.EntityViewService; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.timeseries.TimeseriesService; -import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.usagestats.TbApiUsageClient; @@ -119,11 +121,12 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer @Override public void saveAndNotify(TenantId tenantId, EntityId entityId, List ts, long ttl, FutureCallback callback) { checkInternalEntity(entityId); - if (apiUsageStateService.getApiUsageState(tenantId).isDbStorageEnabled()) { + boolean sysTenant = TenantId.SYS_TENANT_ID.equals(tenantId) || tenantId == null; + if (sysTenant || apiUsageStateService.getApiUsageState(tenantId).isDbStorageEnabled()) { saveAndNotifyInternal(tenantId, entityId, ts, ttl, new FutureCallback() { @Override public void onSuccess(Integer result) { - if (result != null && result > 0) { + if (!sysTenant && result != null && result > 0) { apiUsageClient.report(tenantId, ApiUsageRecordKey.STORAGE_DP_COUNT, result); } callback.onSuccess(null); @@ -134,7 +137,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer callback.onFailure(t); } }); - } else{ + } else { callback.onFailure(new RuntimeException("DB storage writes are disabled due to API limits!")); } } diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 5ceb9b3cd6..98f5708bad 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -54,12 +54,16 @@ import org.thingsboard.server.dao.device.provision.ProvisionResponse; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.DeviceCredentialsProto; import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsgOrBuilder; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionResponseStatus; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; @@ -174,6 +178,8 @@ public class DefaultTransportApiService implements TransportApiService { if (!checkMqttCredentials(mqtt, credentials)) { credentials = null; } + } else { + return getEmptyTransportApiResponseFuture(); } } if (credentials == null) { @@ -292,30 +298,32 @@ public class DefaultTransportApiService implements TransportApiService { requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceSecret())))); } catch (ProvisionFailedException e) { return Futures.immediateFuture(getTransportApiResponseMsg( - TransportProtos.DeviceCredentialsProto.getDefaultInstance(), + new DeviceCredentials(), TransportProtos.ProvisionResponseStatus.valueOf(e.getMessage()))); } - return Futures.transform(provisionResponseFuture, provisionResponse -> getTransportApiResponseMsg( - getDeviceCredentials(provisionResponse.getDeviceCredentials()), TransportProtos.ProvisionResponseStatus.SUCCESS), + return Futures.transform(provisionResponseFuture, provisionResponse -> getTransportApiResponseMsg(provisionResponse.getDeviceCredentials(), TransportProtos.ProvisionResponseStatus.SUCCESS), dbCallbackExecutorService); } - private TransportApiResponseMsg getTransportApiResponseMsg(TransportProtos.DeviceCredentialsProto deviceCredentials, TransportProtos.ProvisionResponseStatus status) { - return TransportApiResponseMsg.newBuilder() - .setProvisionDeviceResponseMsg(TransportProtos.ProvisionDeviceResponseMsg.newBuilder() - .setDeviceCredentials(deviceCredentials) - .setProvisionResponseStatus(status) - .build()) - .build(); - } + private TransportApiResponseMsg getTransportApiResponseMsg(DeviceCredentials deviceCredentials, TransportProtos.ProvisionResponseStatus status) { + if (!status.equals(ProvisionResponseStatus.SUCCESS)) { + return TransportApiResponseMsg.newBuilder().setProvisionDeviceResponseMsg(TransportProtos.ProvisionDeviceResponseMsg.newBuilder().setStatus(status).build()).build(); + } + TransportProtos.ProvisionDeviceResponseMsg.Builder provisionResponse = TransportProtos.ProvisionDeviceResponseMsg.newBuilder() + .setCredentialsType(TransportProtos.CredentialsType.valueOf(deviceCredentials.getCredentialsType().name())) + .setStatus(status); + switch (deviceCredentials.getCredentialsType()){ + case ACCESS_TOKEN: + provisionResponse.setCredentialsValue(deviceCredentials.getCredentialsId()); + break; + case MQTT_BASIC: + case X509_CERTIFICATE: + provisionResponse.setCredentialsValue(deviceCredentials.getCredentialsValue()); + break; + } - private TransportProtos.DeviceCredentialsProto getDeviceCredentials(DeviceCredentials deviceCredentials) { - return TransportProtos.DeviceCredentialsProto.newBuilder() - .setDeviceIdMSB(deviceCredentials.getDeviceId().getId().getMostSignificantBits()) - .setDeviceIdLSB(deviceCredentials.getDeviceId().getId().getLeastSignificantBits()) - .setCredentialsType(TransportProtos.CredentialsType.valueOf(deviceCredentials.getCredentialsType().name())) - .setCredentialsId(deviceCredentials.getCredentialsId()) - .setCredentialsValue(deviceCredentials.getCredentialsValue() != null ? deviceCredentials.getCredentialsValue() : "") + return TransportApiResponseMsg.newBuilder() + .setProvisionDeviceResponseMsg(provisionResponse.build()) .build(); } diff --git a/application/src/main/resources/i18n/messages.properties b/application/src/main/resources/i18n/messages.properties index a34582e132..bdbd64384b 100644 --- a/application/src/main/resources/i18n/messages.properties +++ b/application/src/main/resources/i18n/messages.properties @@ -3,4 +3,5 @@ activation.subject=Your account activation on Thingsboard account.activated.subject=Thingsboard - your account has been activated reset.password.subject=Thingsboard - Password reset has been requested password.was.reset.subject=Thingsboard - your account password has been reset -account.lockout.subject=Thingsboard - User account has been lockout \ No newline at end of file +account.lockout.subject=Thingsboard - User account has been lockout +api.usage.state=Thingsboard - Account limits \ No newline at end of file diff --git a/application/src/main/resources/templates/state.disabled.ftl b/application/src/main/resources/templates/state.disabled.ftl new file mode 100644 index 0000000000..2e341dbe20 --- /dev/null +++ b/application/src/main/resources/templates/state.disabled.ftl @@ -0,0 +1,145 @@ +<#-- + + Copyright © 2016-2020 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. + +--> + + + + + + Thingsboard - Api Usage State + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + +
Your ThingsBoard account feature was disabled
+
+
We have disabled the ${apiFeature} for your account because ThingsBoard has already ${apiLimitValueLabel}.
+
+
Please contact your system administrator to resolve the issue.
— The ThingsBoard
+
+ + + + + + +
This email was sent to ${targetEmail} by ThingsBoard.
+ + diff --git a/application/src/main/resources/templates/state.enabled.ftl b/application/src/main/resources/templates/state.enabled.ftl new file mode 100644 index 0000000000..e03f38bba3 --- /dev/null +++ b/application/src/main/resources/templates/state.enabled.ftl @@ -0,0 +1,142 @@ +<#-- + + Copyright © 2016-2020 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. + +--> + + + + + + Thingsboard - Api Usage State + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Your ThingsBoard account feature was enabled
+
+
We have enabled the ${apiFeature} for your account and ThingsBoard is already able to ${apiLabel} messages.
+
+
— The ThingsBoard
+
+ + + + + + +
This email was sent to ${targetEmail} by ThingsBoard.
+ + diff --git a/application/src/main/resources/templates/state.warning.ftl b/application/src/main/resources/templates/state.warning.ftl new file mode 100644 index 0000000000..67fc557e87 --- /dev/null +++ b/application/src/main/resources/templates/state.warning.ftl @@ -0,0 +1,145 @@ +<#-- + + Copyright © 2016-2020 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. + +--> + + + + + + Thingsboard - Api Usage State + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + +
Warning: your ThingsBoard account feature may be disabled soon
+
+
ThingsBoard has already ${apiValueLabel}.
${apiFeature} will be disabled for your account once the limit will be reached.
+
+
Please contact your system administrator to resolve the issue.
— The ThingsBoard
+
+ + + + + + +
This email was sent to ${targetEmail} by ThingsBoard.
+ + diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 2766793acc..791b032352 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -64,9 +64,9 @@ server: # Minimum value of the server side RPC timeout. May override value provided in the REST API call. # Since 2.5 migration to queues, the RPC delay depends on the size of the pending messages in the queue, # so default UI parameter of 500ms may not be sufficient for loaded environments. - min_timeout: "${MIN_SERVER_SIDE_RPC_TIMEOUT:5000}" + min_timeout: "${MIN_SERVER_SIDE_RPC_TIMEOUT:5000}" # Default value of the server side RPC timeout. - default_timeout: "${DEFAULT_SERVER_SIDE_RPC_TIMEOUT:10000}" + default_timeout: "${DEFAULT_SERVER_SIDE_RPC_TIMEOUT:10000}" # Zookeeper connection parameters. Used for service discovery. zk: @@ -281,8 +281,12 @@ actors: js_thread_pool_size: "${ACTORS_RULE_JS_THREAD_POOL_SIZE:50}" # Specify thread pool size for mail sender executor service mail_thread_pool_size: "${ACTORS_RULE_MAIL_THREAD_POOL_SIZE:50}" + # Specify thread pool size for sms sender executor service + sms_thread_pool_size: "${ACTORS_RULE_SMS_THREAD_POOL_SIZE:50}" # Whether to allow usage of system mail service for rules allow_system_mail_service: "${ACTORS_RULE_ALLOW_SYSTEM_MAIL_SERVICE:true}" + # Whether to allow usage of system sms service for rules + allow_system_sms_service: "${ACTORS_RULE_ALLOW_SYSTEM_SMS_SERVICE:true}" # Specify thread pool size for external call service external_call_thread_pool_size: "${ACTORS_RULE_EXTERNAL_CALL_THREAD_POOL_SIZE:50}" chain: @@ -518,7 +522,7 @@ transport: # Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check) max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}" client_side_rpc: - timeout: "${CLIENT_SIDE_RPC_TIMEOUT:60000}" + timeout: "${CLIENT_SIDE_RPC_TIMEOUT:60000}" # Enable/disable http/mqtt/coap transport protocols (has higher priority than certain protocol's 'enabled' property) api_enabled: "${TB_TRANSPORT_API_ENABLED:true}" # Local HTTP transport parameters @@ -591,6 +595,7 @@ queue: linger.ms: "${TB_KAFKA_LINGER_MS:1}" buffer.memory: "${TB_BUFFER_MEMORY:33554432}" replication_factor: "${TB_QUEUE_KAFKA_REPLICATION_FACTOR:1}" + max_poll_interval_ms: "${TB_QUEUE_KAFKA_MAX_POLL_INTERVAL_MS:300000}" max_poll_records: "${TB_QUEUE_KAFKA_MAX_POLL_RECORDS:8192}" max_partition_fetch_bytes: "${TB_QUEUE_KAFKA_MAX_PARTITION_FETCH_BYTES:16777216}" fetch_max_bytes: "${TB_QUEUE_KAFKA_FETCH_MAX_BYTES:134217728}" @@ -690,8 +695,6 @@ queue: max_requests_timeout: "${REMOTE_JS_MAX_REQUEST_TIMEOUT:10000}" # JS response poll interval response_poll_interval: "${REMOTE_JS_RESPONSE_POLL_INTERVAL_MS:25}" - # JS response auto commit interval - response_auto_commit_interval: "${REMOTE_JS_RESPONSE_AUTO_COMMIT_INTERVAL_MS:100}" rule-engine: topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb_rule_engine}" poll-interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}" diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index cd4a87d4de..c2bff23192 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.controller; -import com.datastax.oss.driver.api.core.uuid.Uuids; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -33,12 +32,7 @@ import org.junit.Rule; import org.junit.rules.TestRule; import org.junit.rules.TestWatcher; import org.junit.runner.Description; -import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootContextLoader; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; @@ -46,10 +40,6 @@ import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.mock.http.MockHttpInputMessage; import org.springframework.mock.http.MockHttpOutputMessage; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultActions; @@ -58,7 +48,6 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilde import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.context.WebApplicationContext; -import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileType; @@ -68,11 +57,13 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; -import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.common.data.id.HasId; -import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.security.Authority; @@ -330,7 +321,7 @@ public abstract class AbstractWebTest { } } - protected DeviceProfile createDeviceProfile(String name) { + protected DeviceProfile createDeviceProfile(String name, DeviceProfileTransportConfiguration deviceProfileTransportConfiguration) { DeviceProfile deviceProfile = new DeviceProfile(); deviceProfile.setName(name); deviceProfile.setType(DeviceProfileType.DEFAULT); @@ -338,15 +329,34 @@ public abstract class AbstractWebTest { deviceProfile.setDescription(name + " Test"); DeviceProfileData deviceProfileData = new DeviceProfileData(); DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); - DefaultDeviceProfileTransportConfiguration transportConfiguration = new DefaultDeviceProfileTransportConfiguration(); deviceProfileData.setConfiguration(configuration); - deviceProfileData.setTransportConfiguration(transportConfiguration); + if (deviceProfileTransportConfiguration != null) { + deviceProfileData.setTransportConfiguration(deviceProfileTransportConfiguration); + } else { + deviceProfileData.setTransportConfiguration(new DefaultDeviceProfileTransportConfiguration()); + } deviceProfile.setProfileData(deviceProfileData); deviceProfile.setDefault(false); deviceProfile.setDefaultRuleChainId(null); return deviceProfile; } + protected MqttDeviceProfileTransportConfiguration createMqttDeviceProfileTransportConfiguration(TransportPayloadTypeConfiguration transportPayloadTypeConfiguration) { + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = new MqttDeviceProfileTransportConfiguration(); + mqttDeviceProfileTransportConfiguration.setDeviceTelemetryTopic(MqttTopics.DEVICE_TELEMETRY_TOPIC); + mqttDeviceProfileTransportConfiguration.setDeviceTelemetryTopic(MqttTopics.DEVICE_ATTRIBUTES_TOPIC); + mqttDeviceProfileTransportConfiguration.setTransportPayloadTypeConfiguration(transportPayloadTypeConfiguration); + return mqttDeviceProfileTransportConfiguration; + } + + protected ProtoTransportPayloadConfiguration createProtoTransportPayloadConfiguration(String deviceAttributesProtoSchema, String deviceTelemetryProtoSchema) { + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = new ProtoTransportPayloadConfiguration(); + protoTransportPayloadConfiguration.setDeviceAttributesProtoSchema(deviceAttributesProtoSchema); + protoTransportPayloadConfiguration.setDeviceTelemetryProtoSchema(deviceTelemetryProtoSchema); + return protoTransportPayloadConfiguration; + } + + protected ResultActions doGet(String urlTemplate, Object... urlVariables) throws Exception { MockHttpServletRequestBuilder getRequest = get(urlTemplate, urlVariables); setJwtToken(getRequest); diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java index 3376c16573..841e899473 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java @@ -16,6 +16,12 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; +import com.github.os72.protobuf.dynamic.DynamicSchema; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.util.JsonFormat; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -28,7 +34,10 @@ import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; @@ -36,9 +45,13 @@ import org.thingsboard.server.common.data.security.Authority; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public abstract class BaseDeviceProfileControllerTest extends AbstractControllerTest { @@ -78,7 +91,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testSaveDeviceProfile() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); Assert.assertNotNull(savedDeviceProfile); Assert.assertNotNull(savedDeviceProfile.getId()); @@ -96,7 +109,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testFindDeviceProfileById() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString(), DeviceProfile.class); Assert.assertNotNull(foundDeviceProfile); @@ -105,7 +118,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testFindDeviceProfileInfoById() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); DeviceProfileInfo foundDeviceProfileInfo = doGet("/api/deviceProfileInfo/"+savedDeviceProfile.getId().getId().toString(), DeviceProfileInfo.class); Assert.assertNotNull(foundDeviceProfileInfo); @@ -127,7 +140,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testSetDefaultDeviceProfile() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile 1"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile 1", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); DeviceProfile defaultDeviceProfile = doPost("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString()+"/default", null, DeviceProfile.class); Assert.assertNotNull(defaultDeviceProfile); @@ -147,19 +160,19 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testSaveDeviceProfileWithSameName() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); doPost("/api/deviceProfile", deviceProfile).andExpect(status().isOk()); - DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile", null); doPost("/api/deviceProfile", deviceProfile2).andExpect(status().isBadRequest()) .andExpect(statusReason(containsString("Device profile with such name already exists"))); } @Test public void testSaveDeviceProfileWithSameProvisionDeviceKey() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); deviceProfile.setProvisionDeviceKey("testProvisionDeviceKey"); doPost("/api/deviceProfile", deviceProfile).andExpect(status().isOk()); - DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile 2"); + DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile 2", null); deviceProfile2.setProvisionDeviceKey("testProvisionDeviceKey"); doPost("/api/deviceProfile", deviceProfile2).andExpect(status().isBadRequest()) .andExpect(statusReason(containsString("Device profile with such provision device key already exists"))); @@ -168,7 +181,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Ignore @Test public void testChangeDeviceProfileTypeWithExistingDevices() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); Device device = new Device(); device.setName("Test device"); @@ -183,7 +196,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testChangeDeviceProfileTransportTypeWithExistingDevices() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); Device device = new Device(); device.setName("Test device"); @@ -197,7 +210,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testDeleteDeviceProfileWithExistingDevice() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); Device device = new Device(); @@ -214,7 +227,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testDeleteDeviceProfile() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); doDelete("/api/deviceProfile/" + savedDeviceProfile.getId().getId().toString()) @@ -235,7 +248,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController deviceProfiles.addAll(pageData.getData()); for (int i=0;i<28;i++) { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i, null); deviceProfiles.add(doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class)); } @@ -280,7 +293,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController deviceProfiles.addAll(deviceProfilePageData.getData()); for (int i=0;i<28;i++) { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i, null); deviceProfiles.add(doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class)); } @@ -318,4 +331,341 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController Assert.assertEquals(1, pageData.getTotalElements()); } + @Test + public void testSaveProtoDeviceProfileWithInvalidProtoFile() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SchemaValidationTest {\n" + + " required int32 parameter = 1;\n" + + "}", "[Transport Configuration] failed to parse attributes proto schema due to: Syntax error in :6:4: 'required' label forbidden in proto3 field declarations"); + } + + @Test + public void testSaveProtoDeviceProfileWithInvalidProtoSyntax() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto2\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SchemaValidationTest {\n" + + " required int32 parameter = 1;\n" + + "}", "[Transport Configuration] invalid schema syntax: proto2 for attributes proto schema provided! Only proto3 allowed!"); + } + + @Test + public void testSaveProtoDeviceProfileOptionsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "option java_package = \"com.test.schemavalidation\";\n" + + "option java_multiple_files = true;\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SchemaValidationTest {\n" + + " int32 parameter = 1;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Schema options don't support!"); + } + + @Test + public void testSaveProtoDeviceProfilePublicImportsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "import public \"oldschema.proto\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SchemaValidationTest {\n" + + " int32 parameter = 1;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Schema public imports don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileImportsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "import \"oldschema.proto\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SchemaValidationTest {\n" + + " int32 parameter = 1;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Schema imports don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileExtendDeclarationsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "extend google.protobuf.MethodOptions {\n" + + " MyMessage my_method_option = 50007;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Schema extend declarations don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileEnumOptionsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "enum testEnum {\n" + + " option allow_alias = true;\n" + + " DEFAULT = 0;\n" + + " STARTED = 1;\n" + + " RUNNING = 2;\n" + + "}\n" + + "\n" + + "message testMessage {\n" + + " int32 parameter = 1;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Enum definitions options are not supported!"); + } + + @Test + public void testSaveProtoDeviceProfileNoOneMessageTypeExists() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "enum testEnum {\n" + + " DEFAULT = 0;\n" + + " STARTED = 1;\n" + + " RUNNING = 2;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! At least one Message definition should exists!"); + } + + @Test + public void testSaveProtoDeviceProfileMessageTypeOptionsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message testMessage {\n" + + " option allow_alias = true;\n" + + " int32 parameter = 1;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Message definition options don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileMessageTypeExtensionsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message TestMessage {\n" + + " extensions 100 to 199;\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Message definition extensions don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileMessageTypeReservedElementsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message Foo {\n" + + " reserved 2, 15, 9 to 11;\n" + + " reserved \"foo\", \"bar\";\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Message definition reserved elements don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileMessageTypeGroupsElementsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message TestMessage {\n" + + " repeated group Result = 1 {\n" + + " string url = 2;\n" + + " string title = 3;\n" + + " repeated string snippets = 4;\n" + + " }\n" + + "}", "[Transport Configuration] invalid attributes proto schema provided! Message definition groups don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileOneOfsGroupsElementsNotSupported() throws Exception { + testSaveDeviceProfileWithInvalidProtoSchema("syntax = \"proto3\";\n" + + "\n" + + "package schemavalidation;\n" + + "\n" + + "message SampleMessage {\n" + + " oneof test_oneof {\n" + + " string name = 1;\n" + + " group Result = 2 {\n" + + " \tstring url = 3;\n" + + " \tstring title = 4;\n" + + " \trepeated string snippets = 5;\n" + + " }\n" + + " }" + + "}", "[Transport Configuration] invalid attributes proto schema provided! OneOf definition groups don't support!"); + } + + @Test + public void testSaveProtoDeviceProfileWithMessageNestedTypes() throws Exception { + String schema = "syntax = \"proto3\";\n" + + "\n" + + "package testnested;\n" + + "\n" + + "message Outer {\n" + + " message MiddleAA {\n" + + " message Inner {\n" + + " int64 ival = 1;\n" + + " bool booly = 2;\n" + + " }\n" + + " Inner inner = 1;\n" + + " }\n" + + " message MiddleBB {\n" + + " message Inner {\n" + + " int32 ival = 1;\n" + + " bool booly = 2;\n" + + " }\n" + + " Inner inner = 1;\n" + + " }\n" + + " MiddleAA middleAA = 1;\n" + + " MiddleBB middleBB = 2;\n" + + "}"; + DynamicSchema dynamicSchema = getDynamicSchema(schema); + assertNotNull(dynamicSchema); + Set messageTypes = dynamicSchema.getMessageTypes(); + assertEquals(5, messageTypes.size()); + assertTrue(messageTypes.contains("testnested.Outer")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleAA")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleAA.Inner")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleBB")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleBB.Inner")); + + DynamicMessage.Builder middleAAInnerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA.Inner"); + Descriptors.Descriptor middleAAInnerMsgDescriptor = middleAAInnerMsgBuilder.getDescriptorForType(); + DynamicMessage middleAAInnerMsg = middleAAInnerMsgBuilder + .setField(middleAAInnerMsgDescriptor.findFieldByName("ival"), 1L) + .setField(middleAAInnerMsgDescriptor.findFieldByName("booly"), true) + .build(); + + DynamicMessage.Builder middleAAMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA"); + Descriptors.Descriptor middleAAMsgDescriptor = middleAAMsgBuilder.getDescriptorForType(); + DynamicMessage middleAAMsg = middleAAMsgBuilder + .setField(middleAAMsgDescriptor.findFieldByName("inner"), middleAAInnerMsg) + .build(); + + DynamicMessage.Builder middleBBInnerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA.Inner"); + Descriptors.Descriptor middleBBInnerMsgDescriptor = middleBBInnerMsgBuilder.getDescriptorForType(); + DynamicMessage middleBBInnerMsg = middleBBInnerMsgBuilder + .setField(middleBBInnerMsgDescriptor.findFieldByName("ival"), 0L) + .setField(middleBBInnerMsgDescriptor.findFieldByName("booly"), false) + .build(); + + DynamicMessage.Builder middleBBMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleBB"); + Descriptors.Descriptor middleBBMsgDescriptor = middleBBMsgBuilder.getDescriptorForType(); + DynamicMessage middleBBMsg = middleBBMsgBuilder + .setField(middleBBMsgDescriptor.findFieldByName("inner"), middleBBInnerMsg) + .build(); + + + DynamicMessage.Builder outerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer"); + Descriptors.Descriptor outerMsgBuilderDescriptor = outerMsgBuilder.getDescriptorForType(); + DynamicMessage outerMsg = outerMsgBuilder + .setField(outerMsgBuilderDescriptor.findFieldByName("middleAA"), middleAAMsg) + .setField(outerMsgBuilderDescriptor.findFieldByName("middleBB"), middleBBMsg) + .build(); + + assertEquals("{\n" + + " \"middleAA\": {\n" + + " \"inner\": {\n" + + " \"ival\": \"1\",\n" + + " \"booly\": true\n" + + " }\n" + + " },\n" + + " \"middleBB\": {\n" + + " \"inner\": {\n" + + " \"ival\": 0,\n" + + " \"booly\": false\n" + + " }\n" + + " }\n" + + "}", dynamicMsgToJson(outerMsgBuilderDescriptor, outerMsg.toByteArray())); + } + + @Test + public void testSaveProtoDeviceProfileWithMessageOneOfs() throws Exception { + String schema = "syntax = \"proto3\";\n" + + "\n" + + "package testoneofs;\n" + + "\n" + + "message SubMessage {\n" + + " repeated string name = 1;\n" + + "}\n" + + "\n" + + "message SampleMessage {\n" + + " oneof testOneOf {\n" + + " string name = 4;\n" + + " SubMessage subMessage = 9;\n" + + " }\n" + + "}"; + DynamicSchema dynamicSchema = getDynamicSchema(schema); + assertNotNull(dynamicSchema); + Set messageTypes = dynamicSchema.getMessageTypes(); + assertEquals(2, messageTypes.size()); + assertTrue(messageTypes.contains("testoneofs.SubMessage")); + assertTrue(messageTypes.contains("testoneofs.SampleMessage")); + + DynamicMessage.Builder sampleMsgBuilder = dynamicSchema.newMessageBuilder("testoneofs.SampleMessage"); + Descriptors.Descriptor sampleMsgDescriptor = sampleMsgBuilder.getDescriptorForType(); + assertNotNull(sampleMsgDescriptor); + + List fields = sampleMsgDescriptor.getFields(); + assertEquals(2, fields.size()); + DynamicMessage sampleMsg = sampleMsgBuilder + .setField(sampleMsgDescriptor.findFieldByName("name"), "Bob") + .build(); + assertEquals("{\n" + " \"name\": \"Bob\"\n" + "}", dynamicMsgToJson(sampleMsgDescriptor, sampleMsg.toByteArray())); + + DynamicMessage.Builder subMsgBuilder = dynamicSchema.newMessageBuilder("testoneofs.SubMessage"); + Descriptors.Descriptor subMsgDescriptor = subMsgBuilder.getDescriptorForType(); + DynamicMessage subMsg = subMsgBuilder + .addRepeatedField(subMsgDescriptor.findFieldByName("name"), "Alice") + .addRepeatedField(subMsgDescriptor.findFieldByName("name"), "John") + .build(); + + DynamicMessage sampleMsgWithOneOfSubMessage = sampleMsgBuilder.setField(sampleMsgDescriptor.findFieldByName("subMessage"), subMsg).build(); + assertEquals("{\n" + " \"subMessage\": {\n" + " \"name\": [\"Alice\", \"John\"]\n" + " }\n" + "}", + dynamicMsgToJson(sampleMsgDescriptor, sampleMsgWithOneOfSubMessage.toByteArray())); + } + + private DeviceProfile testSaveDeviceProfileWithProtoPayloadType(String schema) throws Exception { + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = this.createProtoTransportPayloadConfiguration(schema, schema); + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = this.createMqttDeviceProfileTransportConfiguration(protoTransportPayloadConfiguration); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", mqttDeviceProfileTransportConfiguration); + DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); + DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString(), DeviceProfile.class); + Assert.assertEquals(savedDeviceProfile.getName(), foundDeviceProfile.getName()); + return savedDeviceProfile; + } + + private void testSaveDeviceProfileWithInvalidProtoSchema(String schema, String errorMsg) throws Exception { + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = this.createProtoTransportPayloadConfiguration(schema, schema); + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = this.createMqttDeviceProfileTransportConfiguration(protoTransportPayloadConfiguration); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", mqttDeviceProfileTransportConfiguration); + doPost("/api/deviceProfile", deviceProfile).andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(errorMsg))); + } + + private DynamicSchema getDynamicSchema(String schema) throws Exception { + DeviceProfile deviceProfile = testSaveDeviceProfileWithProtoPayloadType(schema); + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttDeviceProfileTransportConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + ProtoFileElement protoFile = protoTransportPayloadConfiguration.getTransportProtoSchema(schema); + return protoTransportPayloadConfiguration.getDynamicSchema(protoFile, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); + } + + private String dynamicMsgToJson(Descriptors.Descriptor descriptor, byte[] payload) throws InvalidProtocolBufferException { + DynamicMessage dynamicMessage = DynamicMessage.parseFrom(descriptor, payload); + return JsonFormat.printer().includingDefaultValueFields().print(dynamicMessage); + } + } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java index 687269c30c..a1e4bc0e63 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java @@ -38,7 +38,10 @@ import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileCon import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.DeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.controller.AbstractControllerTest; @@ -47,7 +50,6 @@ import org.thingsboard.server.gen.transport.TransportProtos; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -60,6 +62,48 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest private static final AtomicInteger atomicInteger = new AtomicInteger(2); + public static final String DEVICE_TELEMETRY_PROTO_SCHEMA = "syntax =\"proto3\";\n" + + "\n" + + "package test;\n" + + "\n" + + "message PostTelemetry {\n" + + " string key1 = 1;\n" + + " bool key2 = 2;\n" + + " double key3 = 3;\n" + + " int32 key4 = 4;\n" + + " JsonObject key5 = 5;\n" + + "\n" + + " message JsonObject {\n" + + " int32 someNumber = 6;\n" + + " repeated int32 someArray = 7;\n" + + " NestedJsonObject someNestedObject = 8;\n" + + " message NestedJsonObject {\n" + + " string key = 9;\n" + + " }\n" + + " }\n" + + "}"; + + public static final String DEVICE_ATTRIBUTES_PROTO_SCHEMA = "syntax =\"proto3\";\n" + + "\n" + + "package test;\n" + + "\n" + + "message PostAttributes {\n" + + " string key1 = 1;\n" + + " bool key2 = 2;\n" + + " double key3 = 3;\n" + + " int32 key4 = 4;\n" + + " JsonObject key5 = 5;\n" + + "\n" + + " message JsonObject {\n" + + " int32 someNumber = 6;\n" + + " repeated int32 someArray = 7;\n" + + " NestedJsonObject someNestedObject = 8;\n" + + " message NestedJsonObject {\n" + + " string key = 9;\n" + + " }\n" + + " }\n" + + "}"; + protected Tenant savedTenant; protected User tenantAdmin; @@ -69,8 +113,10 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest protected Device savedGateway; protected String gatewayAccessToken; + protected DeviceProfile deviceProfile; + protected void processBeforeTest (String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception { - this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, DeviceProfileProvisionType.DISABLED, null, null); + this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, null, null, DeviceProfileProvisionType.DISABLED, null, null); } protected void processBeforeTest(String deviceName, @@ -78,6 +124,8 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest TransportPayloadType payloadType, String telemetryTopic, String attributesTopic, + String telemetryProtoSchema, + String attributesProtoSchema, DeviceProfileProvisionType provisionType, String provisionKey, String provisionSecret ) throws Exception { @@ -109,12 +157,12 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest gateway.setAdditionalInfo(additionalInfo); if (payloadType != null) { - DeviceProfile mqttDeviceProfile = createMqttDeviceProfile(payloadType, telemetryTopic, attributesTopic, provisionType, provisionKey, provisionSecret); - DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", mqttDeviceProfile, DeviceProfile.class); - device.setType(savedDeviceProfile.getName()); - device.setDeviceProfileId(savedDeviceProfile.getId()); - gateway.setType(savedDeviceProfile.getName()); - gateway.setDeviceProfileId(savedDeviceProfile.getId()); + DeviceProfile mqttDeviceProfile = createMqttDeviceProfile(payloadType, telemetryTopic, attributesTopic, telemetryProtoSchema, attributesProtoSchema, provisionType, provisionKey, provisionSecret); + deviceProfile = doPost("/api/deviceProfile", mqttDeviceProfile, DeviceProfile.class); + device.setType(deviceProfile.getName()); + device.setDeviceProfileId(deviceProfile.getId()); + gateway.setType(deviceProfile.getName()); + gateway.setDeviceProfileId(deviceProfile.getId()); } savedDevice = doPost("/api/device", device, Device.class); @@ -201,9 +249,9 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest protected DeviceProfile createMqttDeviceProfile(TransportPayloadType transportPayloadType, String telemetryTopic, String attributesTopic, + String telemetryProtoSchema, String attributesProtoSchema, DeviceProfileProvisionType provisionType, - String provisionKey, String provisionSecret - ) { + String provisionKey, String provisionSecret) { DeviceProfile deviceProfile = new DeviceProfile(); deviceProfile.setName(transportPayloadType.name()); deviceProfile.setType(DeviceProfileType.DEFAULT); @@ -213,15 +261,30 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest deviceProfile.setDescription(transportPayloadType.name() + " Test"); DeviceProfileData deviceProfileData = new DeviceProfileData(); DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); - MqttDeviceProfileTransportConfiguration transportConfiguration = new MqttDeviceProfileTransportConfiguration(); - transportConfiguration.setTransportPayloadType(transportPayloadType); + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = new MqttDeviceProfileTransportConfiguration(); if (!StringUtils.isEmpty(telemetryTopic)) { - transportConfiguration.setDeviceTelemetryTopic(telemetryTopic); + mqttDeviceProfileTransportConfiguration.setDeviceTelemetryTopic(telemetryTopic); } if (!StringUtils.isEmpty(attributesTopic)) { - transportConfiguration.setDeviceAttributesTopic(attributesTopic); + mqttDeviceProfileTransportConfiguration.setDeviceAttributesTopic(attributesTopic); } - deviceProfileData.setTransportConfiguration(transportConfiguration); + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration; + if (TransportPayloadType.JSON.equals(transportPayloadType)) { + transportPayloadTypeConfiguration = new JsonTransportPayloadConfiguration(); + } else { + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = new ProtoTransportPayloadConfiguration(); + if (StringUtils.isEmpty(telemetryProtoSchema)) { + telemetryProtoSchema = DEVICE_TELEMETRY_PROTO_SCHEMA; + } + if (StringUtils.isEmpty(attributesProtoSchema)) { + attributesProtoSchema = DEVICE_ATTRIBUTES_PROTO_SCHEMA; + } + protoTransportPayloadConfiguration.setDeviceTelemetryProtoSchema(telemetryProtoSchema); + protoTransportPayloadConfiguration.setDeviceAttributesProtoSchema(attributesProtoSchema); + transportPayloadTypeConfiguration = protoTransportPayloadConfiguration; + } + mqttDeviceProfileTransportConfiguration.setTransportPayloadTypeConfiguration(transportPayloadTypeConfiguration); + deviceProfileData.setTransportConfiguration(mqttDeviceProfileTransportConfiguration); DeviceProfileProvisionConfiguration provisionConfiguration; switch (provisionType) { case ALLOW_CREATE_NEW_DEVICES: @@ -233,6 +296,7 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest case DISABLED: default: provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(provisionSecret); + break; } deviceProfileData.setProvisionConfiguration(provisionConfiguration); deviceProfileData.setConfiguration(configuration); diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java index 32488c8eb0..d1b0c482f8 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java @@ -56,7 +56,6 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt return tsKvProtoList; } - protected TransportProtos.TsKvProto getTsKvProto(String key, String value, TransportProtos.KeyValueType keyValueType) { TransportProtos.TsKvProto.Builder tsKvProtoBuilder = TransportProtos.TsKvProto.newBuilder(); TransportProtos.KeyValueProto keyValueProto = getKeyValueProto(key, value, keyValueType); diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java index 9e4529caf2..51d38e6312 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java @@ -18,9 +18,7 @@ package org.thingsboard.server.mqtt.attributes.request; import com.google.protobuf.InvalidProtocolBufferException; import io.netty.handler.codec.mqtt.MqttQoS; import lombok.extern.slf4j.Slf4j; -import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; -import org.eclipse.paho.client.mqttv3.MqttCallback; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.junit.After; @@ -36,9 +34,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @Slf4j diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestJsonIntegrationTest.java index 4b824ea0b5..83ef04f65e 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestJsonIntegrationTest.java @@ -18,14 +18,9 @@ package org.thingsboard.server.mqtt.attributes.request; import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - @Slf4j public abstract class AbstractMqttAttributesRequestJsonIntegrationTest extends AbstractMqttAttributesRequestIntegrationTest { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java index 96cc88fa2f..9e112c98be 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java @@ -15,18 +15,26 @@ */ package org.thingsboard.server.mqtt.attributes.request; +import com.github.os72.protobuf.dynamic.DynamicSchema; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; import com.google.protobuf.InvalidProtocolBufferException; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; import io.netty.handler.codec.mqtt.MqttQoS; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.junit.After; -import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; @@ -38,16 +46,33 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @Slf4j public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends AbstractMqttAttributesRequestIntegrationTest { - @Before - public void beforeTest() throws Exception { - processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", TransportPayloadType.PROTOBUF, null, null); - } + public static final String ATTRIBUTES_SCHEMA_STR = "syntax =\"proto3\";\n" + + "\n" + + "package test;\n" + + "\n" + + "message PostAttributes {\n" + + " string attribute1 = 1;\n" + + " bool attribute2 = 2;\n" + + " double attribute3 = 3;\n" + + " int32 attribute4 = 4;\n" + + " JsonObject attribute5 = 5;\n" + + "\n" + + " message JsonObject {\n" + + " int32 someNumber = 6;\n" + + " repeated int32 someArray = 7;\n" + + " NestedJsonObject someNestedObject = 8;\n" + + " message NestedJsonObject {\n" + + " string key = 9;\n" + + " }\n" + + " }\n" + + "}"; @After public void afterTest() throws Exception { @@ -56,21 +81,55 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends @Test public void testRequestAttributesValuesFromTheServer() throws Exception { + super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", + TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, DeviceProfileProvisionType.DISABLED, null, null); processTestRequestAttributesValuesFromTheServer(); } - @Test public void testRequestAttributesValuesFromTheServerGateway() throws Exception { + super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", TransportPayloadType.PROTOBUF, null, null); processTestGatewayRequestAttributesValuesFromTheServer(); } protected void postAttributesAndSubscribeToTopic(Device savedDevice, MqttAsyncClient client) throws Exception { doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); - String keys = "attribute1,attribute2,attribute3,attribute4,attribute5"; - List expectedKeys = Arrays.asList(keys.split(",")); - TransportProtos.PostAttributeMsg postAttributeMsg = getPostAttributeMsg(expectedKeys); - byte[] payload = postAttributeMsg.toByteArray(); + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); + MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttTransportConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(ATTRIBUTES_SCHEMA_STR); + DynamicSchema attributesSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); + + DynamicMessage.Builder nestedJsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject.NestedJsonObject"); + Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); + assertNotNull(nestedJsonObjectBuilderDescriptor); + DynamicMessage nestedJsonObject = nestedJsonObjectBuilder.setField(nestedJsonObjectBuilderDescriptor.findFieldByName("key"), "value").build(); + + DynamicMessage.Builder jsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject"); + Descriptors.Descriptor jsonObjectBuilderDescriptor = jsonObjectBuilder.getDescriptorForType(); + assertNotNull(jsonObjectBuilderDescriptor); + DynamicMessage jsonObject = jsonObjectBuilder + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNumber"), 42) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 1) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 2) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 3) + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNestedObject"), nestedJsonObject) + .build(); + + DynamicMessage.Builder postAttributesBuilder = attributesSchema.newMessageBuilder("PostAttributes"); + Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType(); + assertNotNull(postAttributesMsgDescriptor); + DynamicMessage postAttributesMsg = postAttributesBuilder + .setField(postAttributesMsgDescriptor.findFieldByName("attribute1"), "value1") + .setField(postAttributesMsgDescriptor.findFieldByName("attribute2"), true) + .setField(postAttributesMsgDescriptor.findFieldByName("attribute3"), 42.0) + .setField(postAttributesMsgDescriptor.findFieldByName("attribute4"), 73) + .setField(postAttributesMsgDescriptor.findFieldByName("attribute5"), jsonObject) + .build(); + byte[] payload = postAttributesMsg.toByteArray(); client.publish(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, new MqttMessage(payload)); client.subscribe(MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC, MqttQoS.AT_MOST_ONCE.value()); } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestJsonSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestJsonSqlIntegrationTest.java index d3750d49a3..146972bade 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestJsonSqlIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestJsonSqlIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.attributes.request.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestIntegrationTest; import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestJsonIntegrationTest; @DaoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestProtoSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestProtoSqlIntegrationTest.java index f52e79b2ab..52ec27f6f8 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestProtoSqlIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestProtoSqlIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.attributes.request.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestJsonIntegrationTest; import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestProtoIntegrationTest; @DaoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java index d2febdf357..b91739790c 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java @@ -18,11 +18,7 @@ package org.thingsboard.server.mqtt.attributes.updates; import com.google.protobuf.InvalidProtocolBufferException; import io.netty.handler.codec.mqtt.MqttQoS; import lombok.extern.slf4j.Slf4j; -import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; -import org.eclipse.paho.client.mqttv3.MqttCallback; -import org.eclipse.paho.client.mqttv3.MqttException; -import org.eclipse.paho.client.mqttv3.MqttMessage; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -33,12 +29,10 @@ import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.mqtt.attributes.AbstractMqttAttributesIntegrationTest; import java.nio.charset.StandardCharsets; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @Slf4j diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java index 58379e4016..e41ff83291 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java @@ -21,11 +21,6 @@ import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - @Slf4j public abstract class AbstractMqttAttributesUpdatesJsonIntegrationTest extends AbstractMqttAttributesUpdatesIntegrationTest { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java index faf8e1ce4d..213540699b 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java @@ -21,11 +21,9 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; -import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; -import java.nio.charset.StandardCharsets; import java.util.List; import java.util.stream.Collectors; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlJsonIntegrationTest.java index a8fd4687f7..1841340835 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlJsonIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.attributes.updates.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesIntegrationTest; import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesJsonIntegrationTest; @DaoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlProtoIntegrationTest.java index 723e5e3dcd..7ff542a4ec 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlProtoIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.attributes.updates.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesJsonIntegrationTest; import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesProtoIntegrationTest; @DaoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimDeviceTest.java index dd16c17be2..5a1cbd6356 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimDeviceTest.java @@ -20,7 +20,6 @@ import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.ClaimRequest; import org.thingsboard.server.common.data.Customer; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimJsonDeviceTest.java index f55cfa57c8..49aa6c995b 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimJsonDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimJsonDeviceTest.java @@ -18,7 +18,6 @@ package org.thingsboard.server.mqtt.claim; import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimProtoDeviceTest.java index d371c09f37..be0cfc7c81 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimProtoDeviceTest.java @@ -19,7 +19,6 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.gen.transport.TransportApiProtos; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceJsonSqlTest.java b/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceJsonSqlTest.java index da794288f4..543d805721 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceJsonSqlTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceJsonSqlTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.claim.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.claim.AbstractMqttClaimDeviceTest; import org.thingsboard.server.mqtt.claim.AbstractMqttClaimJsonDeviceTest; @DaoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceProtoSqlTest.java b/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceProtoSqlTest.java index a63978e4de..e5d866d6e2 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceProtoSqlTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceProtoSqlTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.claim.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.claim.AbstractMqttClaimJsonDeviceTest; import org.thingsboard.server.mqtt.claim.AbstractMqttClaimProtoDeviceTest; @DaoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java index 7c341029b6..6f96ae1034 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java @@ -94,34 +94,32 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn protected void processTestProvisioningDisabledDevice() throws Exception { - super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.DISABLED, null, null); + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, DeviceProfileProvisionType.DISABLED, null, null); byte[] result = createMqttClientAndPublish().getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString()); - Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("provisionDeviceStatus").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("status").getAsString()); } protected void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); byte[] result = createMqttClientAndPublish().getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); Assert.assertNotNull(createdDevice); - Assert.assertEquals(createdDevice.getId().toString(), response.get("deviceId").getAsString()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); } protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); String requestCredentials = ",\"credentialsType\": \"ACCESS_TOKEN\",\"token\": \"test_token\""; byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); @@ -129,20 +127,18 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); Assert.assertNotNull(createdDevice); - Assert.assertEquals(createdDevice.getId().toString(), response.get("deviceId").getAsString()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "ACCESS_TOKEN"); Assert.assertEquals(deviceCredentials.getCredentialsId(), "test_token"); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); } protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); String requestCredentials = ",\"credentialsType\": \"X509_CERTIFICATE\",\"hash\": \"testHash\""; byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); @@ -150,12 +146,10 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); Assert.assertNotNull(createdDevice); - Assert.assertEquals(createdDevice.getId().toString(), response.get("deviceId").getAsString()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "X509_CERTIFICATE"); String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue()); @@ -164,12 +158,12 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn Assert.assertEquals(deviceCredentials.getCredentialsId(), sha3Hash); Assert.assertEquals(deviceCredentials.getCredentialsValue(), "testHash"); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); } protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); String requestCredentials = ",\"credentialsType\": \"MQTT_BASIC\",\"clientId\": \"test_clientId\",\"username\": \"test_username\",\"password\": \"test_password\""; byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); @@ -177,12 +171,10 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); Assert.assertNotNull(createdDevice); - Assert.assertEquals(createdDevice.getId().toString(), response.get("deviceId").getAsString()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "MQTT_BASIC"); Assert.assertEquals(deviceCredentials.getCredentialsId(), EncryptionUtil.getSha3Hash("|", "test_clientId", "test_username")); @@ -192,29 +184,26 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn mqttCredentials.setPassword("test_password"); Assert.assertEquals(deviceCredentials.getCredentialsValue(), JacksonUtil.toString(mqttCredentials)); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); } protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception { - super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret"); byte[] result = createMqttClientAndPublish().getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); - Assert.assertEquals(savedDevice.getId().toString(), response.get("deviceId").getAsString()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), savedDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); } protected void processTestProvisioningWithBadKeyDevice() throws Exception { - super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret"); + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret"); byte[] result = createMqttClientAndPublish().getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString()); - Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("provisionDeviceStatus").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("status").getAsString()); } protected TestMqttCallback createMqttClientAndPublish() throws Exception { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java index 12d8f91eb6..9f74946cad 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java @@ -102,30 +102,28 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI protected void processTestProvisioningDisabledDevice() throws Exception { - super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.DISABLED, null, null); + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.DISABLED, null, null); ProvisionDeviceResponseMsg result = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); Assert.assertNotNull(result); - Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), result.getProvisionResponseStatus().toString()); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), result.getStatus().toString()); } protected void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); Assert.assertNotNull(createdDevice); - Assert.assertEquals(createdDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); - Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().toString()); } protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null,null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken("test_token").build()).build(); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.ACCESS_TOKEN, requestCredentials)).getPayloadBytes()); @@ -133,19 +131,17 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); Assert.assertNotNull(createdDevice); - Assert.assertEquals(createdDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); - Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.ACCESS_TOKEN); Assert.assertEquals(deviceCredentials.getCredentialsId(), "test_token"); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().toString()); } protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceX509CertRequestMsg(ValidateDeviceX509CertRequestMsg.newBuilder().setHash("testHash").build()).build(); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.X509_CERTIFICATE, requestCredentials)).getPayloadBytes()); @@ -153,12 +149,10 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); Assert.assertNotNull(createdDevice); - Assert.assertEquals(createdDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); - Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.X509_CERTIFICATE); String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue()); @@ -167,11 +161,11 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI Assert.assertEquals(deviceCredentials.getCredentialsId(), sha3Hash); Assert.assertEquals(deviceCredentials.getCredentialsValue(), "testHash"); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().toString()); } protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateBasicMqttCredRequestMsg( ValidateBasicMqttCredRequestMsg.newBuilder() .setClientId("test_clientId") @@ -185,12 +179,10 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); Assert.assertNotNull(createdDevice); - Assert.assertEquals(createdDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); - Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.MQTT_BASIC); Assert.assertEquals(deviceCredentials.getCredentialsId(), EncryptionUtil.getSha3Hash("|", "test_clientId", "test_username")); @@ -200,26 +192,23 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI mqttCredentials.setPassword("test_password"); Assert.assertEquals(deviceCredentials.getCredentialsValue(), JacksonUtil.toString(mqttCredentials)); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().toString()); } protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception { - super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret"); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); - Assert.assertEquals(savedDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), savedDevice.getId()); - Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString()); - Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().toString()); } protected void processTestProvisioningWithBadKeyDevice() throws Exception { - super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret"); + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret"); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); - Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.getProvisionResponseStatus().toString()); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.getStatus().toString()); } protected TestMqttCallback createMqttClientAndPublish() throws Exception { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java index 23b93f427e..e3de80e813 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java @@ -15,49 +15,14 @@ */ package org.thingsboard.server.mqtt.rpc; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.google.protobuf.InvalidProtocolBufferException; -import com.nimbusds.jose.util.StandardCharset; import com.datastax.oss.driver.api.core.uuid.Uuids; -import io.netty.handler.codec.mqtt.MqttQoS; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; -import org.eclipse.paho.client.mqttv3.MqttAsyncClient; -import org.eclipse.paho.client.mqttv3.MqttCallback; -import org.eclipse.paho.client.mqttv3.MqttConnectOptions; -import org.eclipse.paho.client.mqttv3.MqttException; -import org.eclipse.paho.client.mqttv3.MqttMessage; import org.junit.After; import org.junit.Assert; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.DeviceProfileType; -import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.TransportPayloadType; -import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; -import org.thingsboard.server.common.data.device.profile.DeviceProfileData; -import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.MqttTopics; -import org.thingsboard.server.common.data.security.Authority; -import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.controller.AbstractControllerTest; -import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.service.security.AccessValidator; -import java.util.Arrays; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** diff --git a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java index e08f1665a4..208aa03a48 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java @@ -15,9 +15,7 @@ */ package org.thingsboard.server.mqtt.rpc; -import com.datastax.oss.driver.api.core.uuid.Uuids; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.protobuf.InvalidProtocolBufferException; import com.nimbusds.jose.util.StandardCharset; import io.netty.handler.codec.mqtt.MqttQoS; @@ -26,35 +24,18 @@ import org.apache.commons.lang3.StringUtils; import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.eclipse.paho.client.mqttv3.MqttCallback; -import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; -import org.junit.After; import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.DeviceProfileType; -import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TransportPayloadType; -import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; -import org.thingsboard.server.common.data.device.profile.DeviceProfileData; -import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.MqttTopics; -import org.thingsboard.server.common.data.security.Authority; -import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest; -import org.thingsboard.server.service.security.AccessValidator; import java.util.Arrays; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcJsonIntegrationTest.java index d9ff14e1d2..2bfb0c1f2d 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcJsonIntegrationTest.java @@ -19,7 +19,6 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java index 759a5da912..062a074c0d 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java @@ -22,16 +22,12 @@ import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - @Slf4j public abstract class AbstractMqttServerSideRpcProtoIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java index 5ac0746a43..0c8e963d1b 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.mqtt.telemetry.attributes; +import com.fasterxml.jackson.core.JsonProcessingException; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.junit.After; @@ -142,7 +143,7 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt } - protected void assertAttributesValues(List> deviceValues, Set expectedKeySet) { + protected void assertAttributesValues(List> deviceValues, Set expectedKeySet) throws JsonProcessingException { for (Map map : deviceValues) { String key = (String) map.get("key"); Object value = map.get("value"); diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java index e9adf19359..1e14541d23 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java @@ -15,18 +15,24 @@ */ package org.thingsboard.server.mqtt.telemetry.attributes; +import com.github.os72.protobuf.dynamic.DynamicSchema; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; import lombok.extern.slf4j.Slf4j; import org.junit.After; -import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; import java.util.Arrays; import java.util.List; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -35,11 +41,6 @@ public abstract class AbstractMqttAttributesProtoIntegrationTest extends Abstrac private static final String POST_DATA_ATTRIBUTES_TOPIC = "proto/attributes"; - @Before - public void beforeTest() throws Exception { - processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC); - } - @After public void afterTest() throws Exception { processAfterTest(); @@ -47,13 +48,49 @@ public abstract class AbstractMqttAttributesProtoIntegrationTest extends Abstrac @Test public void testPushMqttAttributes() throws Exception { + super.processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); - TransportProtos.PostAttributeMsg msg = getPostAttributeMsg(expectedKeys); - processAttributesTest(POST_DATA_ATTRIBUTES_TOPIC, expectedKeys, msg.toByteArray()); + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); + MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttTransportConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + ProtoFileElement transportProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_ATTRIBUTES_PROTO_SCHEMA); + DynamicSchema attributesSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchemaFile, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); + + DynamicMessage.Builder nestedJsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject.NestedJsonObject"); + Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); + assertNotNull(nestedJsonObjectBuilderDescriptor); + DynamicMessage nestedJsonObject = nestedJsonObjectBuilder.setField(nestedJsonObjectBuilderDescriptor.findFieldByName("key"), "value").build(); + + DynamicMessage.Builder jsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject"); + Descriptors.Descriptor jsonObjectBuilderDescriptor = jsonObjectBuilder.getDescriptorForType(); + assertNotNull(jsonObjectBuilderDescriptor); + DynamicMessage jsonObject = jsonObjectBuilder + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNumber"), 42) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 1) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 2) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 3) + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNestedObject"), nestedJsonObject) + .build(); + + DynamicMessage.Builder postAttributesBuilder = attributesSchema.newMessageBuilder("PostAttributes"); + Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType(); + assertNotNull(postAttributesMsgDescriptor); + DynamicMessage postAttributesMsg = postAttributesBuilder + .setField(postAttributesMsgDescriptor.findFieldByName("key1"), "value1") + .setField(postAttributesMsgDescriptor.findFieldByName("key2"), true) + .setField(postAttributesMsgDescriptor.findFieldByName("key3"), 3.0) + .setField(postAttributesMsgDescriptor.findFieldByName("key4"), 4) + .setField(postAttributesMsgDescriptor.findFieldByName("key5"), jsonObject) + .build(); + processAttributesTest(POST_DATA_ATTRIBUTES_TOPIC, expectedKeys, postAttributesMsg.toByteArray()); } @Test public void testPushMqttAttributesGateway() throws Exception { + super.processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, null); TransportApiProtos.GatewayAttributesMsg.Builder gatewayAttributesMsgProtoBuilder = TransportApiProtos.GatewayAttributesMsg.newBuilder(); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); String deviceName1 = "Device A"; diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlJsonIntegrationTest.java index 68ab8cff62..53a6f4adde 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlJsonIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.telemetry.attributes.nosql; import org.thingsboard.server.dao.service.DaoNoSqlTest; -import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesIntegrationTest; import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesJsonIntegrationTest; @DaoNoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlProtoIntegrationTest.java index d508fd76c5..23b6052272 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlProtoIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.telemetry.attributes.nosql; import org.thingsboard.server.dao.service.DaoNoSqlTest; -import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesIntegrationTest; import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesProtoIntegrationTest; @DaoNoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/sql/MqttAttributesSqlProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/sql/MqttAttributesSqlProtoIntegrationTest.java index 5f486916ae..c5eeb06546 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/sql/MqttAttributesSqlProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/sql/MqttAttributesSqlProtoIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.telemetry.attributes.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesJsonIntegrationTest; import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesProtoIntegrationTest; @DaoSqlTest diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java index f6294cd990..64ba73c374 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java @@ -27,7 +27,6 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest; @@ -128,7 +127,34 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt } else { getTelemetryValuesUrl = "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?keys=" + String.join(",", actualKeySet); } - Map>> values = doGetAsync(getTelemetryValuesUrl, Map.class); + start = System.currentTimeMillis(); + end = System.currentTimeMillis() + 5000; + Map>> values = null; + while (start <= end) { + values = doGetAsync(getTelemetryValuesUrl, Map.class); + boolean valid = values.size() == expectedKeys.size(); + if (valid) { + for (String key : expectedKeys) { + List> tsValues = values.get(key); + if (tsValues != null && tsValues.size() > 0) { + Object ts = tsValues.get(0).get("ts"); + if (ts == null) { + valid = false; + break; + } + } else { + valid = false; + break; + } + } + } + if (valid) { + break; + } + Thread.sleep(100); + start += 100; + } + assertNotNull(values); if (withTs) { assertTs(values, expectedKeys, 10000, 0); diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java index edab8405cd..181a35fa93 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java @@ -27,7 +27,6 @@ import org.thingsboard.server.common.data.device.profile.MqttTopics; import java.util.Arrays; import java.util.List; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @Slf4j diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java index 2257350d31..4cb2f0271c 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java @@ -15,33 +15,36 @@ */ package org.thingsboard.server.mqtt.telemetry.timeseries; +import com.github.os72.protobuf.dynamic.DynamicSchema; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.junit.After; -import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; import java.util.Arrays; import java.util.List; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; @Slf4j public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends AbstractMqttTimeseriesIntegrationTest { private static final String POST_DATA_TELEMETRY_TOPIC = "proto/telemetry"; - @Before - public void beforeTest() throws Exception { - processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null); - } - @After public void afterTest() throws Exception { processAfterTest(); @@ -49,20 +52,127 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac @Test public void testPushMqttTelemetry() throws Exception { + super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); - TransportProtos.TsKvListProto tsKvListProto = getTsKvListProto(expectedKeys, 0); - processTelemetryTest(POST_DATA_TELEMETRY_TOPIC, expectedKeys, tsKvListProto.toByteArray(), false); + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); + MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttTransportConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_TELEMETRY_PROTO_SCHEMA); + DynamicSchema telemetrySchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); + + DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject"); + Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); + assertNotNull(nestedJsonObjectBuilderDescriptor); + DynamicMessage nestedJsonObject = nestedJsonObjectBuilder.setField(nestedJsonObjectBuilderDescriptor.findFieldByName("key"), "value").build(); + + DynamicMessage.Builder jsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject"); + Descriptors.Descriptor jsonObjectBuilderDescriptor = jsonObjectBuilder.getDescriptorForType(); + assertNotNull(jsonObjectBuilderDescriptor); + DynamicMessage jsonObject = jsonObjectBuilder + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNumber"), 42) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 1) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 2) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 3) + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNestedObject"), nestedJsonObject) + .build(); + + DynamicMessage.Builder postTelemetryBuilder = telemetrySchema.newMessageBuilder("PostTelemetry"); + Descriptors.Descriptor postTelemetryMsgDescriptor = postTelemetryBuilder.getDescriptorForType(); + assertNotNull(postTelemetryMsgDescriptor); + DynamicMessage postTelemetryMsg = postTelemetryBuilder + .setField(postTelemetryMsgDescriptor.findFieldByName("key1"), "value1") + .setField(postTelemetryMsgDescriptor.findFieldByName("key2"), true) + .setField(postTelemetryMsgDescriptor.findFieldByName("key3"), 3.0) + .setField(postTelemetryMsgDescriptor.findFieldByName("key4"), 4) + .setField(postTelemetryMsgDescriptor.findFieldByName("key5"), jsonObject) + .build(); + processTelemetryTest(POST_DATA_TELEMETRY_TOPIC, expectedKeys, postTelemetryMsg.toByteArray(), false); } @Test public void testPushMqttTelemetryWithTs() throws Exception { + String schemaStr = "syntax =\"proto3\";\n" + + "\n" + + "package test;\n" + + "\n" + + "message PostTelemetry {\n" + + " int64 ts = 1;\n" + + " Values values = 2;\n" + + " \n" + + " message Values {\n" + + " string key1 = 3;\n" + + " bool key2 = 4;\n" + + " double key3 = 5;\n" + + " int32 key4 = 6;\n" + + " JsonObject key5 = 7;\n" + + " }\n" + + " \n" + + " message JsonObject {\n" + + " int32 someNumber = 8;\n" + + " repeated int32 someArray = 9;\n" + + " NestedJsonObject someNestedObject = 10;\n" + + " message NestedJsonObject {\n" + + " string key = 11;\n" + + " }\n" + + " }\n" + + "}"; + super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, schemaStr, null, DeviceProfileProvisionType.DISABLED, null, null); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); - TransportProtos.TsKvListProto tsKvListProto = getTsKvListProto(expectedKeys, 10000); - processTelemetryTest(POST_DATA_TELEMETRY_TOPIC, expectedKeys, tsKvListProto.toByteArray(), true); + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); + MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttTransportConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(schemaStr); + DynamicSchema telemetrySchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); + + DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject"); + Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); + assertNotNull(nestedJsonObjectBuilderDescriptor); + DynamicMessage nestedJsonObject = nestedJsonObjectBuilder.setField(nestedJsonObjectBuilderDescriptor.findFieldByName("key"), "value").build(); + + DynamicMessage.Builder jsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject"); + Descriptors.Descriptor jsonObjectBuilderDescriptor = jsonObjectBuilder.getDescriptorForType(); + assertNotNull(jsonObjectBuilderDescriptor); + DynamicMessage jsonObject = jsonObjectBuilder + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNumber"), 42) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 1) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 2) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 3) + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNestedObject"), nestedJsonObject) + .build(); + + + DynamicMessage.Builder valuesBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.Values"); + Descriptors.Descriptor valuesDescriptor = valuesBuilder.getDescriptorForType(); + assertNotNull(valuesDescriptor); + + DynamicMessage valuesMsg = valuesBuilder + .setField(valuesDescriptor.findFieldByName("key1"), "value1") + .setField(valuesDescriptor.findFieldByName("key2"), true) + .setField(valuesDescriptor.findFieldByName("key3"), 3.0) + .setField(valuesDescriptor.findFieldByName("key4"), 4) + .setField(valuesDescriptor.findFieldByName("key5"), jsonObject) + .build(); + + DynamicMessage.Builder postTelemetryBuilder = telemetrySchema.newMessageBuilder("PostTelemetry"); + Descriptors.Descriptor postTelemetryMsgDescriptor = postTelemetryBuilder.getDescriptorForType(); + assertNotNull(postTelemetryMsgDescriptor); + DynamicMessage postTelemetryMsg = postTelemetryBuilder + .setField(postTelemetryMsgDescriptor.findFieldByName("ts"), 10000L) + .setField(postTelemetryMsgDescriptor.findFieldByName("values"), valuesMsg) + .build(); + + processTelemetryTest(POST_DATA_TELEMETRY_TOPIC, expectedKeys, postTelemetryMsg.toByteArray(), true); } @Test public void testPushMqttTelemetryGateway() throws Exception { + super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.DISABLED, null, null); TransportApiProtos.GatewayTelemetryMsg.Builder gatewayTelemetryMsgProtoBuilder = TransportApiProtos.GatewayTelemetryMsg.newBuilder(); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); String deviceName1 = "Device A"; @@ -76,6 +186,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac @Test public void testGatewayConnect() throws Exception { + super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, null, null, DeviceProfileProvisionType.DISABLED, null, null); String deviceName = "Device A"; TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName); MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlJsonIntegrationTest.java index f313cc5d29..0b494b6bea 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlJsonIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.telemetry.timeseries.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesIntegrationTest; import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesJsonIntegrationTest; /** diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlProtoIntegrationTest.java index 3c7d5e2e2a..2a80483879 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlProtoIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.mqtt.telemetry.timeseries.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesJsonIntegrationTest; import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesProtoIntegrationTest; /** diff --git a/common/actor/pom.xml b/common/actor/pom.xml index 9258ee75a4..9dea3c57a3 100644 --- a/common/actor/pom.xml +++ b/common/actor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT common org.thingsboard.common diff --git a/common/dao-api/pom.xml b/common/dao-api/pom.xml index 650b05c04e..d50399525e 100644 --- a/common/dao-api/pom.xml +++ b/common/dao-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT common org.thingsboard.common diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java index 3af4737f06..2e5c895f84 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java @@ -16,6 +16,7 @@ package org.thingsboard.server.dao.attributes; import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; @@ -38,4 +39,7 @@ public interface AttributesService { ListenableFuture> save(TenantId tenantId, EntityId entityId, String scope, List attributes); ListenableFuture> removeAll(TenantId tenantId, EntityId entityId, String scope, List attributeKeys); + + List findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId); + } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java index 5d038b2d31..660f1a5f35 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java @@ -15,9 +15,6 @@ */ package org.thingsboard.server.dao.device; -import com.google.common.util.concurrent.ListenableFuture; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionResponse; diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TbTenantProfileCache.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TbTenantProfileCache.java index 735e23e73a..ea3d65eb49 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TbTenantProfileCache.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TbTenantProfileCache.java @@ -16,9 +16,12 @@ package org.thingsboard.server.dao.tenant; import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; +import java.util.function.Consumer; + public interface TbTenantProfileCache { TenantProfile get(TenantId tenantId); @@ -31,4 +34,8 @@ public interface TbTenantProfileCache { void evict(TenantId id); + void addListener(TenantId tenantId, EntityId listenerId, Consumer profileListener); + + void removeListener(TenantId tenantId, EntityId listenerId); + } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java index 0e11aedaf5..dbfbe3d0f5 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java @@ -16,6 +16,7 @@ package org.thingsboard.server.dao.timeseries; import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; @@ -47,4 +48,6 @@ public interface TimeseriesService { ListenableFuture> removeLatest(TenantId tenantId, EntityId entityId, Collection keys); ListenableFuture> removeAllLatest(TenantId tenantId, EntityId entityId); + + List findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId); } diff --git a/common/data/pom.xml b/common/data/pom.xml index eb7a3b226c..b2dd156acb 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT common org.thingsboard.common @@ -71,6 +71,14 @@ java-driver-core test
+ + com.squareup.wire + wire-schema + + + org.thingsboard + protobuf-dynamic + diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java index e12cb5fb14..10ae01a699 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java @@ -18,16 +18,21 @@ package org.thingsboard.server.common.data; import lombok.Getter; public enum ApiFeature { - TRANSPORT("transportApiState"), - DB("dbApiState"), - RE("ruleEngineApiState"), - JS("jsExecutionApiState"); + TRANSPORT("transportApiState", "Device API"), + DB("dbApiState", "Telemetry persistence"), + RE("ruleEngineApiState", "Rule Engine execution"), + JS("jsExecutionApiState", "JavaScript functions execution"), + EMAIL("emailApiState", "Email messages"), + SMS("smsApiState", "SMS messages"); @Getter private final String apiStateKey; + @Getter + private final String label; - ApiFeature(String apiStateKey) { + ApiFeature(String apiStateKey, String label) { this.apiStateKey = apiStateKey; + this.label = label; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java index dc8556a755..70b2902256 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java @@ -23,11 +23,15 @@ public enum ApiUsageRecordKey { TRANSPORT_DP_COUNT(ApiFeature.TRANSPORT, "transportDataPointsCount", "transportDataPointsLimit"), STORAGE_DP_COUNT(ApiFeature.DB, "storageDataPointsCount", "storageDataPointsLimit"), RE_EXEC_COUNT(ApiFeature.RE, "ruleEngineExecutionCount", "ruleEngineExecutionLimit"), - JS_EXEC_COUNT(ApiFeature.JS, "jsExecutionCount", "jsExecutionLimit"); + JS_EXEC_COUNT(ApiFeature.JS, "jsExecutionCount", "jsExecutionLimit"), + EMAIL_EXEC_COUNT(ApiFeature.EMAIL, "emailCount", "emailLimit"), + SMS_EXEC_COUNT(ApiFeature.SMS, "smsCount", "smsLimit"); private static final ApiUsageRecordKey[] JS_RECORD_KEYS = {JS_EXEC_COUNT}; private static final ApiUsageRecordKey[] RE_RECORD_KEYS = {RE_EXEC_COUNT}; private static final ApiUsageRecordKey[] DB_RECORD_KEYS = {STORAGE_DP_COUNT}; private static final ApiUsageRecordKey[] TRANSPORT_RECORD_KEYS = {TRANSPORT_MSG_COUNT, TRANSPORT_DP_COUNT}; + private static final ApiUsageRecordKey[] EMAIL_RECORD_KEYS = {EMAIL_EXEC_COUNT}; + private static final ApiUsageRecordKey[] SMS_RECORD_KEYS = {SMS_EXEC_COUNT}; @Getter private final ApiFeature apiFeature; @@ -52,6 +56,10 @@ public enum ApiUsageRecordKey { return RE_RECORD_KEYS; case JS: return JS_RECORD_KEYS; + case EMAIL: + return EMAIL_RECORD_KEYS; + case SMS: + return SMS_RECORD_KEYS; default: return new ApiUsageRecordKey[]{}; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java index 52797baa3b..8f13fa8164 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java @@ -47,6 +47,12 @@ public class ApiUsageState extends BaseData implements HasTenan @Getter @Setter private ApiUsageStateValue jsExecState; + @Getter + @Setter + private ApiUsageStateValue emailExecState; + @Getter + @Setter + private ApiUsageStateValue smsExecState; public ApiUsageState() { super(); @@ -64,6 +70,8 @@ public class ApiUsageState extends BaseData implements HasTenan this.dbStorageState = ur.getDbStorageState(); this.reExecState = ur.getReExecState(); this.jsExecState = ur.getJsExecState(); + this.emailExecState = ur.getEmailExecState(); + this.smsExecState = ur.getSmsExecState(); } public boolean isTransportEnabled() { @@ -81,4 +89,12 @@ public class ApiUsageState extends BaseData implements HasTenan public boolean isJsExecEnabled() { return !ApiUsageStateValue.DISABLED.equals(jsExecState); } + + public boolean isEmailSendEnabled(){ + return !ApiUsageStateValue.DISABLED.equals(emailExecState); + } + + public boolean isSmsSendEnabled(){ + return !ApiUsageStateValue.DISABLED.equals(smsExecState); + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateMailMessage.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateMailMessage.java new file mode 100644 index 0000000000..cab3789fdc --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateMailMessage.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2020 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.common.data; + +import lombok.Data; + +@Data +public class ApiUsageStateMailMessage { + private final ApiUsageRecordKey key; + private final long threshold; + private final long value; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java index 17035f337b..3b6acce6f3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java @@ -18,9 +18,8 @@ package org.thingsboard.server.common.data.device.profile; import lombok.Data; import org.thingsboard.server.common.data.alarm.AlarmSeverity; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; +import java.util.TreeMap; @Data public class DeviceProfileAlarm { @@ -28,7 +27,7 @@ public class DeviceProfileAlarm { private String id; private String alarmType; - private LinkedHashMap createRules; + private TreeMap createRules; private AlarmRule clearRule; // Hidden in advanced settings diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java index 34854958d1..fb337b24f4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileTransportConfiguration.java @@ -29,7 +29,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; property = "type") @JsonSubTypes({ @JsonSubTypes.Type(value = DefaultDeviceProfileTransportConfiguration.class, name = "DEFAULT"), - @JsonSubTypes.Type(value = MqttDeviceProfileTransportConfiguration.class, name = "MQTT"), + @JsonSubTypes.Type(value = MqttDeviceProfileTransportConfiguration.class, name = "MQTT"), @JsonSubTypes.Type(value = Lwm2mDeviceProfileTransportConfiguration.class, name = "LWM2M")}) public interface DeviceProfileTransportConfiguration { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/JsonTransportPayloadConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/JsonTransportPayloadConfiguration.java new file mode 100644 index 0000000000..f4fd93459b --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/JsonTransportPayloadConfiguration.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2020 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.common.data.device.profile; + +import lombok.Data; +import org.thingsboard.server.common.data.TransportPayloadType; + +@Data +public class JsonTransportPayloadConfiguration implements TransportPayloadTypeConfiguration { + + @Override + public TransportPayloadType getTransportPayloadType() { + return TransportPayloadType.JSON; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java index d88ac24cbb..c49b7bd356 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java @@ -16,20 +16,27 @@ package org.thingsboard.server.common.data.device.profile; import lombok.Data; -import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.DeviceTransportType; @Data public class MqttDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { - private TransportPayloadType transportPayloadType = TransportPayloadType.JSON; - private String deviceTelemetryTopic = MqttTopics.DEVICE_TELEMETRY_TOPIC; private String deviceAttributesTopic = MqttTopics.DEVICE_ATTRIBUTES_TOPIC; + private TransportPayloadTypeConfiguration transportPayloadTypeConfiguration; @Override public DeviceTransportType getType() { return DeviceTransportType.MQTT; } + public TransportPayloadTypeConfiguration getTransportPayloadTypeConfiguration() { + if (transportPayloadTypeConfiguration != null) { + return transportPayloadTypeConfiguration; + } else { + return new JsonTransportPayloadConfiguration(); + } + } + + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java new file mode 100644 index 0000000000..08c846d905 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java @@ -0,0 +1,200 @@ +/** + * Copyright © 2016-2020 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.common.data.device.profile; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.github.os72.protobuf.dynamic.DynamicSchema; +import com.github.os72.protobuf.dynamic.EnumDefinition; +import com.github.os72.protobuf.dynamic.MessageDefinition; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; +import com.squareup.wire.schema.Location; +import com.squareup.wire.schema.internal.parser.EnumConstantElement; +import com.squareup.wire.schema.internal.parser.EnumElement; +import com.squareup.wire.schema.internal.parser.FieldElement; +import com.squareup.wire.schema.internal.parser.MessageElement; +import com.squareup.wire.schema.internal.parser.OneOfElement; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; +import com.squareup.wire.schema.internal.parser.ProtoParser; +import com.squareup.wire.schema.internal.parser.TypeElement; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.TransportPayloadType; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Data +public class ProtoTransportPayloadConfiguration implements TransportPayloadTypeConfiguration { + + public static final Location LOCATION = new Location("", "", -1, -1); + public static final String ATTRIBUTES_PROTO_SCHEMA = "attributes proto schema"; + public static final String TELEMETRY_PROTO_SCHEMA = "telemetry proto schema"; + + private String deviceTelemetryProtoSchema; + private String deviceAttributesProtoSchema; + + @Override + public TransportPayloadType getTransportPayloadType() { + return TransportPayloadType.PROTOBUF; + } + + public Descriptors.Descriptor getTelemetryDynamicMessageDescriptor(String deviceTelemetryProtoSchema) { + return getDescriptor(deviceTelemetryProtoSchema, TELEMETRY_PROTO_SCHEMA); + } + + public Descriptors.Descriptor getAttributesDynamicMessageDescriptor(String deviceAttributesProtoSchema) { + return getDescriptor(deviceAttributesProtoSchema, ATTRIBUTES_PROTO_SCHEMA); + } + + private Descriptors.Descriptor getDescriptor(String protoSchema, String schemaName) { + try { + ProtoFileElement protoFileElement = getTransportProtoSchema(protoSchema); + DynamicSchema dynamicSchema = getDynamicSchema(protoFileElement, schemaName); + String lastMsgName = getMessageTypes(protoFileElement.getTypes()).stream() + .map(MessageElement::getName).reduce((previous, last) -> last).get(); + DynamicMessage.Builder builder = dynamicSchema.newMessageBuilder(lastMsgName); + return builder.getDescriptorForType(); + } catch (Exception e) { + log.warn("Failed to get Message Descriptor due to {}", e.getMessage()); + return null; + } + } + + public DynamicSchema getDynamicSchema(ProtoFileElement protoFileElement, String schemaName) { + DynamicSchema.Builder schemaBuilder = DynamicSchema.newBuilder(); + schemaBuilder.setName(schemaName); + schemaBuilder.setPackage(!isEmptyStr(protoFileElement.getPackageName()) ? + protoFileElement.getPackageName() : schemaName.toLowerCase()); + List types = protoFileElement.getTypes(); + List messageTypes = getMessageTypes(types); + + if (!messageTypes.isEmpty()) { + List enumTypes = getEnumElements(types); + if (!enumTypes.isEmpty()) { + enumTypes.forEach(enumElement -> { + EnumDefinition enumDefinition = getEnumDefinition(enumElement); + schemaBuilder.addEnumDefinition(enumDefinition); + }); + } + List messageDefinitions = getMessageDefinitions(messageTypes); + messageDefinitions.forEach(schemaBuilder::addMessageDefinition); + try { + return schemaBuilder.build(); + } catch (Descriptors.DescriptorValidationException e) { + throw new RuntimeException("Failed to create dynamic schema due to: " + e.getMessage()); + } + } else { + throw new RuntimeException("Failed to get Dynamic Schema! Message types is empty for schema:" + schemaName); + } + } + + public ProtoFileElement getTransportProtoSchema(String protoSchema) { + return new ProtoParser(LOCATION, protoSchema.toCharArray()).readProtoFile(); + } + + private List getMessageTypes(List types) { + return types.stream() + .filter(typeElement -> typeElement instanceof MessageElement) + .map(typeElement -> (MessageElement) typeElement) + .collect(Collectors.toList()); + } + + private List getEnumElements(List types) { + return types.stream() + .filter(typeElement -> typeElement instanceof EnumElement) + .map(typeElement -> (EnumElement) typeElement) + .collect(Collectors.toList()); + } + + private List getMessageDefinitions(List messageElementsList) { + if (!messageElementsList.isEmpty()) { + List messageDefinitions = new ArrayList<>(); + messageElementsList.forEach(messageElement -> { + MessageDefinition.Builder messageDefinitionBuilder = MessageDefinition.newBuilder(messageElement.getName()); + + List nestedTypes = messageElement.getNestedTypes(); + if (!nestedTypes.isEmpty()) { + List nestedEnumTypes = getEnumElements(nestedTypes); + if (!nestedEnumTypes.isEmpty()) { + nestedEnumTypes.forEach(enumElement -> { + EnumDefinition nestedEnumDefinition = getEnumDefinition(enumElement); + messageDefinitionBuilder.addEnumDefinition(nestedEnumDefinition); + }); + } + List nestedMessageTypes = getMessageTypes(nestedTypes); + List nestedMessageDefinitions = getMessageDefinitions(nestedMessageTypes); + nestedMessageDefinitions.forEach(messageDefinitionBuilder::addMessageDefinition); + } + List messageElementFields = messageElement.getFields(); + List oneOfs = messageElement.getOneOfs(); + if (!oneOfs.isEmpty()) { + for (OneOfElement oneOfelement : oneOfs) { + MessageDefinition.OneofBuilder oneofBuilder = messageDefinitionBuilder.addOneof(oneOfelement.getName()); + addMessageFieldsToTheOneOfDefinition(oneOfelement.getFields(), oneofBuilder); + } + } + if (!messageElementFields.isEmpty()) { + addMessageFieldsToTheMessageDefinition(messageElementFields, messageDefinitionBuilder); + } + messageDefinitions.add(messageDefinitionBuilder.build()); + }); + return messageDefinitions; + } else { + return Collections.emptyList(); + } + } + + private EnumDefinition getEnumDefinition(EnumElement enumElement) { + List enumElementTypeConstants = enumElement.getConstants(); + EnumDefinition.Builder enumDefinitionBuilder = EnumDefinition.newBuilder(enumElement.getName()); + if (!enumElementTypeConstants.isEmpty()) { + enumElementTypeConstants.forEach(constantElement -> enumDefinitionBuilder.addValue(constantElement.getName(), constantElement.getTag())); + } + return enumDefinitionBuilder.build(); + } + + + private void addMessageFieldsToTheMessageDefinition(List messageElementFields, MessageDefinition.Builder messageDefinitionBuilder) { + messageElementFields.forEach(fieldElement -> { + String labelStr = null; + if (fieldElement.getLabel() != null) { + labelStr = fieldElement.getLabel().name().toLowerCase(); + } + messageDefinitionBuilder.addField( + labelStr, + fieldElement.getType(), + fieldElement.getName(), + fieldElement.getTag()); + }); + } + + private void addMessageFieldsToTheOneOfDefinition(List oneOfsElementFields, MessageDefinition.OneofBuilder oneofBuilder) { + oneOfsElementFields.forEach(fieldElement -> oneofBuilder.addField( + fieldElement.getType(), + fieldElement.getName(), + fieldElement.getTag())); + oneofBuilder.msgDefBuilder(); + } + + private boolean isEmptyStr(String str) { + return str == null || "".equals(str); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/TransportPayloadTypeConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/TransportPayloadTypeConfiguration.java new file mode 100644 index 0000000000..5a6bbd5b25 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/TransportPayloadTypeConfiguration.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2020 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.common.data.device.profile; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.thingsboard.server.common.data.TransportPayloadType; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "transportPayloadType") +@JsonSubTypes({ + @JsonSubTypes.Type(value = JsonTransportPayloadConfiguration.class, name = "JSON"), + @JsonSubTypes.Type(value = ProtoTransportPayloadConfiguration.class, name = "PROTOBUF")}) +public interface TransportPayloadTypeConfiguration { + + @JsonIgnore + TransportPayloadType getTransportPayloadType(); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/AwsSnsSmsProviderConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/AwsSnsSmsProviderConfiguration.java new file mode 100644 index 0000000000..2acad17a0f --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/AwsSnsSmsProviderConfiguration.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2020 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.common.data.sms.config; + +import lombok.Data; + +@Data +public class AwsSnsSmsProviderConfiguration implements SmsProviderConfiguration { + + private String accessKeyId; + private String secretAccessKey; + private String region; + + @Override + public SmsProviderType getType() { + return SmsProviderType.AWS_SNS; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmsProviderConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmsProviderConfiguration.java new file mode 100644 index 0000000000..8b1984e9b4 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmsProviderConfiguration.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2020 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.common.data.sms.config; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = AwsSnsSmsProviderConfiguration.class, name = "AWS_SNS"), + @JsonSubTypes.Type(value = TwilioSmsProviderConfiguration.class, name = "TWILIO")}) +public interface SmsProviderConfiguration { + + @JsonIgnore + SmsProviderType getType(); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmsProviderType.java b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmsProviderType.java new file mode 100644 index 0000000000..f60d917cf1 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmsProviderType.java @@ -0,0 +1,21 @@ +/** + * Copyright © 2016-2020 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.common.data.sms.config; + +public enum SmsProviderType { + AWS_SNS, + TWILIO +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TestSmsRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TestSmsRequest.java new file mode 100644 index 0000000000..23c44be48c --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TestSmsRequest.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2020 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.common.data.sms.config; + +import lombok.Data; + +@Data +public class TestSmsRequest { + + private SmsProviderConfiguration providerConfiguration; + private String numberTo; + private String message; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TwilioSmsProviderConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TwilioSmsProviderConfiguration.java new file mode 100644 index 0000000000..6f9b226711 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TwilioSmsProviderConfiguration.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2020 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.common.data.sms.config; + +import lombok.Data; + +@Data +public class TwilioSmsProviderConfiguration implements SmsProviderConfiguration { + + private String accountSid; + private String accountToken; + private String numberFrom; + + @Override + public SmsProviderType getType() { + return SmsProviderType.TWILIO; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java index 250f9c75a5..054f78766b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java @@ -24,6 +24,10 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura private long maxDevices; private long maxAssets; + private long maxCustomers; + private long maxUsers; + private long maxDashboards; + private long maxRuleChains; private String transportTenantMsgRateLimit; private String transportTenantTelemetryMsgRateLimit; @@ -38,6 +42,10 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura private long maxJSExecutions; private long maxDPStorageDays; private int maxRuleNodeExecutionsPerMessage; + private long maxEmails; + private long maxSms; + + private int defaultStorageTtlDays; private double warnThreshold; @@ -54,6 +62,10 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura return maxREExecutions; case STORAGE_DP_COUNT: return maxDPStorageDays; + case EMAIL_EXEC_COUNT: + return maxEmails; + case SMS_EXEC_COUNT: + return maxSms; } return 0L; } diff --git a/common/message/pom.xml b/common/message/pom.xml index 17a0679630..bcff40ce4d 100644 --- a/common/message/pom.xml +++ b/common/message/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT common org.thingsboard.common diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java index 0b257d9d6e..2df7a10320 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java @@ -195,11 +195,15 @@ public final class TbMsg implements Serializable { } public TbMsg copyWithRuleChainId(RuleChainId ruleChainId) { - return new TbMsg(this.queueName, this.id, this.ts, this.type, this.originator, this.metaData, this.dataType, this.data, ruleChainId, null, this.ruleNodeExecCounter.get(), callback); + return copyWithRuleChainId(ruleChainId, this.id); } - public TbMsg copyWithRuleNodeId(RuleChainId ruleChainId, RuleNodeId ruleNodeId) { - return new TbMsg(this.queueName, this.id, this.ts, this.type, this.originator, this.metaData, this.dataType, this.data, ruleChainId, ruleNodeId, this.ruleNodeExecCounter.get(), callback); + public TbMsg copyWithRuleChainId(RuleChainId ruleChainId, UUID msgId) { + return new TbMsg(this.queueName, msgId, this.ts, this.type, this.originator, this.metaData, this.dataType, this.data, ruleChainId, null, this.ruleNodeExecCounter.get(), callback); + } + + public TbMsg copyWithRuleNodeId(RuleChainId ruleChainId, RuleNodeId ruleNodeId, UUID msgId) { + return new TbMsg(this.queueName, msgId, this.ts, this.type, this.originator, this.metaData, this.dataType, this.data, ruleChainId, ruleNodeId, this.ruleNodeExecCounter.get(), callback); } public TbMsgCallback getCallback() { diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/tools/SchedulerUtils.java b/common/message/src/main/java/org/thingsboard/server/common/msg/tools/SchedulerUtils.java index 421066b30b..57cc578f2c 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/tools/SchedulerUtils.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/tools/SchedulerUtils.java @@ -17,22 +17,19 @@ package org.thingsboard.server.common.msg.tools; import java.time.LocalDate; import java.time.LocalDateTime; -import java.time.LocalTime; import java.time.ZoneId; -import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalAdjuster; import java.time.temporal.TemporalAdjusters; -import java.time.temporal.TemporalUnit; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import static java.time.ZoneOffset.UTC; import static java.time.temporal.ChronoField.DAY_OF_MONTH; import static java.time.temporal.ChronoUnit.MONTHS; public class SchedulerUtils { - private final static ZoneId UTC = ZoneId.of("UTC"); private static final ConcurrentMap tzMap = new ConcurrentHashMap<>(); public static ZoneId getZoneId(String tz) { @@ -44,7 +41,7 @@ public class SchedulerUtils { } public static long getStartOfCurrentHour(ZoneId zoneId) { - return LocalDateTime.now(ZoneOffset.UTC).atZone(zoneId).truncatedTo(ChronoUnit.HOURS).toInstant().toEpochMilli(); + return LocalDateTime.now(UTC).atZone(zoneId).truncatedTo(ChronoUnit.HOURS).toInstant().toEpochMilli(); } public static long getStartOfCurrentMonth() { @@ -52,7 +49,7 @@ public class SchedulerUtils { } public static long getStartOfCurrentMonth(ZoneId zoneId) { - return LocalDate.now().withDayOfMonth(1).atStartOfDay(zoneId).toInstant().toEpochMilli(); + return LocalDate.now(UTC).withDayOfMonth(1).atStartOfDay(zoneId).toInstant().toEpochMilli(); } public static long getStartOfNextMonth() { @@ -60,7 +57,7 @@ public class SchedulerUtils { } public static long getStartOfNextMonth(ZoneId zoneId) { - return LocalDate.now().with(TemporalAdjusters.firstDayOfNextMonth()).atStartOfDay(zoneId).toInstant().toEpochMilli(); + return LocalDate.now(UTC).with(TemporalAdjusters.firstDayOfNextMonth()).atStartOfDay(zoneId).toInstant().toEpochMilli(); } public static long getStartOfNextNextMonth() { @@ -68,7 +65,7 @@ public class SchedulerUtils { } public static long getStartOfNextNextMonth(ZoneId zoneId) { - return LocalDate.now().with(firstDayOfNextNextMonth()).atStartOfDay(zoneId).toInstant().toEpochMilli(); + return LocalDate.now(UTC).with(firstDayOfNextNextMonth()).atStartOfDay(zoneId).toInstant().toEpochMilli(); } public static TemporalAdjuster firstDayOfNextNextMonth() { diff --git a/common/pom.xml b/common/pom.xml index 52685d3891..5ceae3ea57 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT thingsboard common diff --git a/common/queue/pom.xml b/common/queue/pom.xml index aaf46710f7..54cd24f66d 100644 --- a/common/queue/pom.xml +++ b/common/queue/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT common org.thingsboard.common diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java index c683ed846d..5cceb43324 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java @@ -195,9 +195,11 @@ public class HashPartitionService implements PartitionService { if (current.getServiceTypesList().contains(serviceType.name())) { result.add(current.getServiceId()); } - for (ServiceInfo serviceInfo : currentOtherServices) { - if (serviceInfo.getServiceTypesList().contains(serviceType.name())) { - result.add(serviceInfo.getServiceId()); + if (currentOtherServices != null) { + for (ServiceInfo serviceInfo : currentOtherServices) { + if (serviceInfo.getServiceTypesList().contains(serviceType.name())) { + result.add(serviceInfo.getServiceId()); + } } } return result; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaAdmin.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaAdmin.java index bee293bd6d..ba8e86a495 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaAdmin.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaAdmin.java @@ -42,7 +42,7 @@ public class TbKafkaAdmin implements TbQueueAdmin { private final short replicationFactor; public TbKafkaAdmin(TbKafkaSettings settings, Map topicConfigs) { - client = AdminClient.create(settings.toProps()); + client = AdminClient.create(settings.toAdminProps()); this.topicConfigs = topicConfigs; try { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java index 1fbfb68e3e..e664a63c02 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java @@ -45,25 +45,14 @@ public class TbKafkaConsumerTemplate extends AbstractTbQue @Builder private TbKafkaConsumerTemplate(TbKafkaSettings settings, TbKafkaDecoder decoder, String clientId, String groupId, String topic, - boolean autoCommit, int autoCommitIntervalMs, - int maxPollRecords, TbQueueAdmin admin) { super(topic); - Properties props = settings.toProps(); + Properties props = settings.toConsumerProps(); props.put(ConsumerConfig.CLIENT_ID_CONFIG, clientId); if (groupId != null) { props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId); } - props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, settings.getMaxPollRecords()); - props.put(ConsumerConfig.MAX_PARTITION_FETCH_BYTES_CONFIG, settings.getMaxPartitionFetchBytes()); - props.put(ConsumerConfig.FETCH_MAX_BYTES_CONFIG, settings.getFetchMaxBytes()); - props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, autoCommit); - props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, autoCommitIntervalMs); - props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer"); - props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.ByteArrayDeserializer"); - if (maxPollRecords > 0) { - props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, maxPollRecords); - } + this.admin = admin; this.consumer = new KafkaConsumer<>(props); this.decoder = decoder; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java index 4f26f51da7..e154c8040c 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java @@ -55,9 +55,8 @@ public class TbKafkaProducerTemplate implements TbQueuePro @Builder private TbKafkaProducerTemplate(TbKafkaSettings settings, String defaultTopic, String clientId, TbQueueAdmin admin) { - Properties props = settings.toProps(); - props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer"); - props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.ByteArraySerializer"); + Properties props = settings.toProducerProps(); + if (!StringUtils.isEmpty(clientId)) { props.put(ProducerConfig.CLIENT_ID_CONFIG, clientId); } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java index 37e978c07d..b5ba1931be 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java @@ -19,9 +19,11 @@ import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.CommonClientConfigs; +import org.apache.kafka.clients.admin.AdminClientConfig; +import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.producer.ProducerConfig; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @@ -32,7 +34,7 @@ import java.util.Properties; * Created by ashvayka on 25.09.18. */ @Slf4j -@ConditionalOnExpression("'${queue.type:null}'=='kafka'") +@ConditionalOnProperty(prefix = "queue", value = "type", havingValue = "kafka") @ConfigurationProperties(prefix = "queue.kafka") @Component public class TbKafkaSettings { @@ -60,15 +62,15 @@ public class TbKafkaSettings { private short replicationFactor; @Value("${queue.kafka.max_poll_records:8192}") - @Getter private int maxPollRecords; + @Value("${queue.kafka.max_poll_interval_ms:300000}") + private int maxPollIntervalMs; + @Value("${queue.kafka.max_partition_fetch_bytes:16777216}") - @Getter private int maxPartitionFetchBytes; @Value("${queue.kafka.fetch_max_bytes:134217728}") - @Getter private int fetchMaxBytes; @Value("${queue.kafka.use_confluent_cloud:false}") @@ -89,21 +91,48 @@ public class TbKafkaSettings { @Setter private List other; - public Properties toProps() { - Properties props = new Properties(); + public Properties toAdminProps() { + Properties props = toProps(); + props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, servers); + props.put(AdminClientConfig.RETRIES_CONFIG, retries); + + return props; + } + + public Properties toConsumerProps() { + Properties props = toProps(); + props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, servers); + props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, maxPollRecords); + props.put(ConsumerConfig.MAX_PARTITION_FETCH_BYTES_CONFIG, maxPartitionFetchBytes); + props.put(ConsumerConfig.FETCH_MAX_BYTES_CONFIG, fetchMaxBytes); + props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, maxPollIntervalMs); + + props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer"); + props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.ByteArrayDeserializer"); + return props; + } + + public Properties toProducerProps() { + Properties props = toProps(); props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, servers); props.put(ProducerConfig.RETRIES_CONFIG, retries); + props.put(ProducerConfig.ACKS_CONFIG, acks); + props.put(ProducerConfig.BATCH_SIZE_CONFIG, batchSize); + props.put(ProducerConfig.LINGER_MS_CONFIG, lingerMs); + props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, bufferMemory); + props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer"); + props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.ByteArraySerializer"); + return props; + } + + private Properties toProps() { + Properties props = new Properties(); if (useConfluent) { props.put("ssl.endpoint.identification.algorithm", sslAlgorithm); props.put("sasl.mechanism", saslMechanism); props.put("sasl.jaas.config", saslConfig); props.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, securityProtocol); - } else { - props.put(ProducerConfig.ACKS_CONFIG, acks); - props.put(ProducerConfig.BATCH_SIZE_CONFIG, batchSize); - props.put(ProducerConfig.LINGER_MS_CONFIG, lingerMs); - props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, bufferMemory); } if (other != null) { @@ -111,4 +140,5 @@ public class TbKafkaSettings { } return props; } + } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java index 4b68abf929..21291a2d7e 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java @@ -17,7 +17,7 @@ package org.thingsboard.server.queue.kafka; import lombok.Getter; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; @@ -25,7 +25,7 @@ import java.util.HashMap; import java.util.Map; @Component -@ConditionalOnExpression("'${queue.type:null}'=='kafka'") +@ConditionalOnProperty(prefix = "queue", value = "type", havingValue = "kafka") public class TbKafkaTopicConfigs { @Value("${queue.kafka.topic-properties.core}") private String coreProperties; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueRemoteJsInvokeSettings.java b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueRemoteJsInvokeSettings.java index cd492e87f1..e34482f985 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueRemoteJsInvokeSettings.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueRemoteJsInvokeSettings.java @@ -34,9 +34,6 @@ public class TbQueueRemoteJsInvokeSettings { @Value("${queue.js.response_poll_interval}") private int responsePollInterval; - @Value("${queue.js.response_auto_commit_interval}") - private int autoCommitInterval; - @Value("${queue.js.max_requests_timeout}") private long maxRequestsTimeout; } diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index 4b5a6028e6..325a517988 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -269,8 +269,9 @@ message ProvisionDeviceCredentialsMsg { } message ProvisionDeviceResponseMsg { - DeviceCredentialsProto deviceCredentials = 1; - ProvisionResponseStatus provisionResponseStatus = 2; + ProvisionResponseStatus status = 1; + CredentialsType credentialsType = 2; + string credentialsValue = 3; } enum ProvisionResponseStatus { diff --git a/common/stats/pom.xml b/common/stats/pom.xml index 10c3ccb040..8cf70c7498 100644 --- a/common/stats/pom.xml +++ b/common/stats/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT common org.thingsboard.common diff --git a/common/transport/coap/pom.xml b/common/transport/coap/pom.xml index 3b04a20abe..e775286bdd 100644 --- a/common/transport/coap/pom.xml +++ b/common/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java index afb83f513f..a6813e488d 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.CoapResource; import org.eclipse.californium.core.CoapServer; import org.eclipse.californium.core.network.CoapEndpoint; +import org.eclipse.californium.core.network.config.NetworkConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; @@ -46,7 +47,7 @@ public class CoapTransportService { public void init() throws UnknownHostException { log.info("Starting CoAP transport..."); log.info("Starting CoAP transport server"); - this.server = new CoapServer(); + this.server = new CoapServer(NetworkConfig.createStandardWithoutFile()); createResources(); InetAddress addr = InetAddress.getByName(coapTransportContext.getHost()); InetSocketAddress sockAddr = new InetSocketAddress(addr, coapTransportContext.getPort()); diff --git a/common/transport/http/pom.xml b/common/transport/http/pom.xml index 040f7c372e..ab60e6d4eb 100644 --- a/common/transport/http/pom.xml +++ b/common/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/mqtt/pom.xml b/common/transport/mqtt/pom.xml index 8a02c940a7..8b043b8c2e 100644 --- a/common/transport/mqtt/pom.xml +++ b/common/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT transport org.thingsboard.common.transport 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 a8bfc3d938..0df14d4655 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 @@ -40,6 +40,7 @@ import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import lombok.extern.slf4j.Slf4j; 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.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; @@ -71,6 +72,7 @@ import java.io.IOException; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -177,6 +179,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } } else { + ctx.close(); throw new RuntimeException("Unsupported topic for provisioning requests!"); } } catch (RuntimeException | AdaptorException e) { @@ -232,7 +235,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement if (topicName.startsWith(MqttTopics.BASE_GATEWAY_API_TOPIC)) { if (gatewaySessionHandler != null) { - handleGatewayPublishMsg(topicName, msgId, mqttMsg); + handleGatewayPublishMsg(ctx, topicName, msgId, mqttMsg); transportService.reportActivity(deviceSessionCtx.getSessionInfo()); } } else { @@ -240,7 +243,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } - private void handleGatewayPublishMsg(String topicName, int msgId, MqttPublishMessage mqttMsg) { + private void handleGatewayPublishMsg(ChannelHandlerContext ctx, String topicName, int msgId, MqttPublishMessage mqttMsg) { try { switch (topicName) { case MqttTopics.GATEWAY_TELEMETRY_TOPIC: @@ -264,6 +267,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement case MqttTopics.GATEWAY_DISCONNECT_TOPIC: gatewaySessionHandler.onDeviceDisconnect(mqttMsg); break; + default: + ack(ctx, msgId); } } catch (RuntimeException | AdaptorException e) { log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); @@ -293,6 +298,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement transportService.process(deviceSessionCtx.getSessionInfo(), claimDeviceMsg, getPubAckCallback(ctx, msgId, claimDeviceMsg)); } else { transportService.reportActivity(deviceSessionCtx.getSessionInfo()); + ack(ctx, msgId); } } catch (AdaptorException e) { log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); @@ -301,15 +307,18 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } + private void ack(ChannelHandlerContext ctx, int msgId) { + if (msgId > 0) { + ctx.writeAndFlush(createMqttPubAckMsg(msgId)); + } + } private TransportServiceCallback getPubAckCallback(final ChannelHandlerContext ctx, final int msgId, final T msg) { return new TransportServiceCallback() { @Override public void onSuccess(Void dummy) { log.trace("[{}] Published msg: {}", sessionId, msg); - if (msgId > 0) { - ctx.writeAndFlush(createMqttPubAckMsg(msgId)); - } + ack(ctx, msgId); } @Override @@ -334,9 +343,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @Override public void onSuccess(TransportProtos.ProvisionDeviceResponseMsg provisionResponseMsg) { log.trace("[{}] Published msg: {}", sessionId, msg); - if (msgId > 0) { - ctx.writeAndFlush(createMqttPubAckMsg(msgId)); - } + ack(ctx, msgId); try { if (deviceSessionCtx.getProvisionPayloadType().equals(TransportPayloadType.JSON)) { deviceSessionCtx.getContext().getJsonMqttAdaptor().convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); @@ -696,7 +703,12 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } @Override - public void onProfileUpdate(DeviceProfile deviceProfile) { - deviceSessionCtx.onProfileUpdate(deviceProfile); + public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) { + deviceSessionCtx.onDeviceProfileUpdate(sessionInfo, deviceProfile); + } + + @Override + public void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional deviceProfileOpt) { + deviceSessionCtx.onDeviceUpdate(sessionInfo, device, deviceProfileOpt); } } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java index 1a0d33cde4..e4a35a435f 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java @@ -15,7 +15,11 @@ */ package org.thingsboard.server.transport.mqtt.adaptors; +import com.google.gson.JsonParser; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.util.JsonFormat; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.UnpooledByteBufAllocator; @@ -29,10 +33,11 @@ import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.adaptor.ProtoConverter; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; +import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionContext; import java.util.Optional; @@ -45,20 +50,24 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { @Override public TransportProtos.PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { + DeviceSessionCtx deviceSessionCtx = (DeviceSessionCtx) ctx; byte[] bytes = toBytes(inbound.payload()); + Descriptors.Descriptor telemetryDynamicMsgDescriptor = getDescriptor(deviceSessionCtx.getTelemetryDynamicMsgDescriptor()); try { - return ProtoConverter.convertToTelemetryProto(bytes); - } catch (InvalidProtocolBufferException | IllegalArgumentException e) { + return JsonConverter.convertToTelemetryProto(new JsonParser().parse(dynamicMsgToJson(bytes, telemetryDynamicMsgDescriptor))); + } catch (Exception e) { throw new AdaptorException(e); } } @Override public TransportProtos.PostAttributeMsg convertToPostAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { + DeviceSessionCtx deviceSessionCtx = (DeviceSessionCtx) ctx; byte[] bytes = toBytes(inbound.payload()); + Descriptors.Descriptor attributesDynamicMessage = getDescriptor(deviceSessionCtx.getAttributesDynamicMessageDescriptor()); try { - return ProtoConverter.validatePostAttributeMsg(bytes); - } catch (InvalidProtocolBufferException | IllegalArgumentException e) { + return JsonConverter.convertToAttributesProto(new JsonParser().parse(dynamicMsgToJson(bytes, attributesDynamicMessage))); + } catch (Exception e) { throw new AdaptorException(e); } } @@ -112,7 +121,6 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { @Override public TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(MqttDeviceAwareSessionContext ctx, MqttPublishMessage mqttMsg) throws AdaptorException { byte[] bytes = toBytes(mqttMsg.payload()); - String topicName = mqttMsg.variableHeader().topicName(); try { return ProtoConverter.convertToProvisionRequestMsg(bytes); } catch (InvalidProtocolBufferException ex) { @@ -207,4 +215,16 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { return Integer.parseInt(topicName.substring(topic.length())); } + private Descriptors.Descriptor getDescriptor(Descriptors.Descriptor descriptor) throws AdaptorException { + if (descriptor == null) { + throw new AdaptorException("Failed to get dynamic message descriptor!"); + } + return descriptor; + } + + private String dynamicMsgToJson(byte[] bytes, Descriptors.Descriptor descriptor) throws InvalidProtocolBufferException { + DynamicMessage dynamicMessage = DynamicMessage.parseFrom(descriptor, bytes); + return JsonFormat.printer().includingDefaultValueFields().print(dynamicMessage); + } + } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java index 20ddfb5a24..487aa8a207 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.mqtt.session; +import com.google.protobuf.Descriptors; import io.netty.channel.ChannelHandlerContext; import lombok.Getter; import lombok.Setter; @@ -24,6 +25,9 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.mqtt.MqttTransportContext; import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; import org.thingsboard.server.transport.mqtt.util.MqttTopicFilter; @@ -54,6 +58,8 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { private volatile MqttTopicFilter telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter(); private volatile MqttTopicFilter attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter(); private volatile TransportPayloadType payloadType = TransportPayloadType.JSON; + private volatile Descriptors.Descriptor attributesDynamicMessageDescriptor; + private volatile Descriptors.Descriptor telemetryDynamicMessageDescriptor; @Getter @Setter @@ -72,7 +78,9 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { return msgIdSeq.incrementAndGet(); } - public boolean isDeviceTelemetryTopic(String topicName) { return telemetryTopicFilter.filter(topicName); } + public boolean isDeviceTelemetryTopic(String topicName) { + return telemetryTopicFilter.filter(topicName); + } public boolean isDeviceAttributesTopic(String topicName) { return attributesTopicFilter.filter(topicName); @@ -86,6 +94,14 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { return payloadType.equals(TransportPayloadType.JSON); } + public Descriptors.Descriptor getTelemetryDynamicMsgDescriptor() { + return telemetryDynamicMessageDescriptor; + } + + public Descriptors.Descriptor getAttributesDynamicMessageDescriptor() { + return attributesDynamicMessageDescriptor; + } + @Override public void setDeviceProfile(DeviceProfile deviceProfile) { super.setDeviceProfile(deviceProfile); @@ -93,24 +109,32 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { } @Override - public void onProfileUpdate(DeviceProfile deviceProfile) { - super.onProfileUpdate(deviceProfile); + public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) { + super.onDeviceProfileUpdate(sessionInfo, deviceProfile); updateTopicFilters(deviceProfile); } - private void updateTopicFilters(DeviceProfile deviceProfile) { DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); if (transportConfiguration.getType().equals(DeviceTransportType.MQTT) && transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) { MqttDeviceProfileTransportConfiguration mqttConfig = (MqttDeviceProfileTransportConfiguration) transportConfiguration; - payloadType = mqttConfig.getTransportPayloadType(); + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttConfig.getTransportPayloadTypeConfiguration(); + payloadType = transportPayloadTypeConfiguration.getTransportPayloadType(); telemetryTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceTelemetryTopic()); attributesTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceAttributesTopic()); + if (TransportPayloadType.PROTOBUF.equals(payloadType)) { + updateDynamicMessageDescriptors(transportPayloadTypeConfiguration); + } } else { telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter(); attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter(); } } + private void updateDynamicMessageDescriptors(TransportPayloadTypeConfiguration transportPayloadTypeConfiguration) { + ProtoTransportPayloadConfiguration protoTransportPayloadConfig = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + telemetryDynamicMessageDescriptor = protoTransportPayloadConfig.getTelemetryDynamicMessageDescriptor(protoTransportPayloadConfig.getDeviceTelemetryProtoSchema()); + attributesDynamicMessageDescriptor = protoTransportPayloadConfig.getAttributesDynamicMessageDescriptor(protoTransportPayloadConfig.getDeviceAttributesProtoSchema()); + } } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/AlwaysTrueTopicFilter.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/AlwaysTrueTopicFilter.java new file mode 100644 index 0000000000..9952c4b507 --- /dev/null +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/AlwaysTrueTopicFilter.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2020 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.util; + +import lombok.Data; + +@Data +public class AlwaysTrueTopicFilter implements MqttTopicFilter { + + @Override + public boolean filter(String topic) { + return true; + } +} diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java index 4d5a9a7c2b..0c3b497591 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java @@ -33,7 +33,9 @@ public class MqttTopicFilterFactory { throw new IllegalArgumentException("Topic filter can't be empty!"); } return filters.computeIfAbsent(topicFilter, filter -> { - if (filter.contains("+") || filter.contains("#")) { + if (filter.equals("#")) { + return new AlwaysTrueTopicFilter(); + } else if (filter.contains("+") || filter.contains("#")) { String regex = filter .replace("\\", "\\\\") .replace("+", "[^/]+") diff --git a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java index 0b854d51ef..fac2e5c01d 100644 --- a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java +++ b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java @@ -20,7 +20,6 @@ import org.junit.runner.RunWith; import org.mockito.runners.MockitoJUnitRunner; import javax.script.ScriptException; -import java.util.regex.Pattern; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -31,6 +30,9 @@ public class MqttTopicFilterFactoryTest { private static String TEST_STR_1 = "Sensor/Temperature/House/48"; private static String TEST_STR_2 = "Sensor/Temperature"; private static String TEST_STR_3 = "Sensor/Temperature2/House/48"; + private static String TEST_STR_4 = "/Sensor/Temperature2/House/48"; + private static String TEST_STR_5 = "Sensor/ Temperature"; + private static String TEST_STR_6 = "/"; @Test public void metadataCanBeUpdated() throws ScriptException { @@ -51,6 +53,17 @@ public class MqttTopicFilterFactoryTest { assertTrue(filter.filter(TEST_STR_1)); assertTrue(filter.filter(TEST_STR_2)); assertFalse(filter.filter(TEST_STR_3)); + + filter = MqttTopicFilterFactory.toFilter("#"); + assertTrue(filter.filter(TEST_STR_1)); + assertTrue(filter.filter(TEST_STR_2)); + assertTrue(filter.filter(TEST_STR_3)); + assertTrue(filter.filter(TEST_STR_4)); + assertTrue(filter.filter(TEST_STR_5)); + assertTrue(filter.filter(TEST_STR_6)); + + filter = MqttTopicFilterFactory.toFilter("Sensor/Temperature#"); + assertFalse(filter.filter(TEST_STR_2)); } } diff --git a/common/transport/pom.xml b/common/transport/pom.xml index fbdb10d081..22b17989c9 100644 --- a/common/transport/pom.xml +++ b/common/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT common org.thingsboard.common diff --git a/common/transport/transport-api/pom.xml b/common/transport/transport-api/pom.xml index 168c991eb6..d61963053e 100644 --- a/common/transport/transport-api/pom.xml +++ b/common/transport/transport-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java index ccd63ca430..9eb0fbcd6c 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java @@ -15,13 +15,17 @@ */ package org.thingsboard.server.common.transport; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto; import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg; +import java.util.Optional; + /** * Created by ashvayka on 04.10.18. */ @@ -37,7 +41,9 @@ public interface SessionMsgListener { void onToServerRpcResponse(ToServerRpcResponseMsg toServerResponse); - default void onProfileUpdate(DeviceProfile deviceProfile) { + default void onDeviceProfileUpdate(TransportProtos.SessionInfoProto newSessionInfo, DeviceProfile deviceProfile) { } + default void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional deviceProfileOpt) { + } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java index de37856985..5ffc5abc7d 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.common.transport; -import org.thingsboard.server.common.msg.session.SessionContext; +import org.thingsboard.server.common.transport.session.SessionContext; public interface TransportAdaptor { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index 321ed22baa..a20f697bba 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -213,13 +213,7 @@ public class JsonConverter { throw new JsonSyntaxException(CAN_T_PARSE_VALUE + value); } } else if (element.isJsonObject() || element.isJsonArray()) { - result.add(KeyValueProto - .newBuilder() - .setKey(valueEntry - .getKey()) - .setType(KeyValueType.JSON_V) - .setJsonV(element.toString()) - .build()); + result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.JSON_V).setJsonV(element.toString()).build()); } else if (!element.isJsonNull()) { throw new JsonSyntaxException(CAN_T_PARSE_VALUE + element); } @@ -414,22 +408,27 @@ public class JsonConverter { private static JsonObject toJson(ProvisionDeviceResponseMsg payload, boolean toGateway, int requestId) { JsonObject result = new JsonObject(); - if (payload.getProvisionResponseStatus() == TransportProtos.ProvisionResponseStatus.NOT_FOUND) { + if (payload.getStatus() == TransportProtos.ProvisionResponseStatus.NOT_FOUND) { result.addProperty("errorMsg", "Provision data was not found!"); - result.addProperty("provisionDeviceStatus", ProvisionResponseStatus.NOT_FOUND.name()); - } else if (payload.getProvisionResponseStatus() == TransportProtos.ProvisionResponseStatus.FAILURE) { + result.addProperty("status", ProvisionResponseStatus.NOT_FOUND.name()); + } else if (payload.getStatus() == TransportProtos.ProvisionResponseStatus.FAILURE) { result.addProperty("errorMsg", "Failed to provision device!"); - result.addProperty("provisionDeviceStatus", ProvisionResponseStatus.FAILURE.name()); + result.addProperty("status", ProvisionResponseStatus.FAILURE.name()); } else { if (toGateway) { result.addProperty("id", requestId); } - result.addProperty("deviceId", new UUID(payload.getDeviceCredentials().getDeviceIdMSB(), payload.getDeviceCredentials().getDeviceIdLSB()).toString()); - result.addProperty("credentialsType", payload.getDeviceCredentials().getCredentialsType().name()); - result.addProperty("credentialsId", payload.getDeviceCredentials().getCredentialsId()); - result.addProperty("credentialsValue", - StringUtils.isEmpty(payload.getDeviceCredentials().getCredentialsValue()) ? null : payload.getDeviceCredentials().getCredentialsValue()); - result.addProperty("provisionDeviceStatus", ProvisionResponseStatus.SUCCESS.name()); + switch (payload.getCredentialsType()) { + case ACCESS_TOKEN: + case X509_CERTIFICATE: + result.addProperty("credentialsValue", payload.getCredentialsValue()); + break; + case MQTT_BASIC: + result.add("credentialsValue", JSON_PARSER.parse(payload.getCredentialsValue()).getAsJsonObject()); + break; + } + result.addProperty("credentialsType", payload.getCredentialsType().name()); + result.addProperty("status", ProvisionResponseStatus.SUCCESS.name()); } return result; } @@ -557,7 +556,7 @@ public class JsonConverter { private static TransportProtos.ProvisionDeviceRequestMsg buildProvisionRequestMsg(JsonObject jo) { return TransportProtos.ProvisionDeviceRequestMsg.newBuilder() - .setDeviceName(getStrValue(jo, DataConstants.DEVICE_NAME, true)) + .setDeviceName(getStrValue(jo, DataConstants.DEVICE_NAME, false)) .setCredentialsType(jo.get(DataConstants.CREDENTIALS_TYPE) != null ? TransportProtos.CredentialsType.valueOf(getStrValue(jo, DataConstants.CREDENTIALS_TYPE, false)) : CredentialsType.ACCESS_TOKEN) .setCredentialsDataProto(TransportProtos.CredentialsDataProto.newBuilder() .setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken(getStrValue(jo, DataConstants.TOKEN, false)).build()) 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 4af723f3a0..3e619f8d6a 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 @@ -27,6 +27,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntityType; @@ -390,7 +391,8 @@ public class DefaultTransportService implements TransportService { metaData.putValue("deviceName", sessionInfo.getDeviceName()); metaData.putValue("deviceType", sessionInfo.getDeviceType()); metaData.putValue("notifyDevice", "false"); - sendToRuleEngine(tenantId, deviceId, sessionInfo, json, metaData, SessionMsgType.POST_ATTRIBUTES_REQUEST, new TransportTbQueueCallback(callback)); + sendToRuleEngine(tenantId, deviceId, sessionInfo, json, metaData, SessionMsgType.POST_ATTRIBUTES_REQUEST, + new TransportTbQueueCallback(new ApiStatsProxyCallback<>(tenantId, msg.getKvList().size(), callback))); } } @@ -399,7 +401,7 @@ public class DefaultTransportService implements TransportService { if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) - .setGetAttributes(msg).build(), callback); + .setGetAttributes(msg).build(), new ApiStatsProxyCallback<>(getTenantId(sessionInfo), 1, callback)); } } @@ -409,7 +411,7 @@ public class DefaultTransportService implements TransportService { SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo); sessionMetaData.setSubscribedToAttributes(!msg.getUnsubscribe()); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) - .setSubscribeToAttributes(msg).build(), callback); + .setSubscribeToAttributes(msg).build(), new ApiStatsProxyCallback<>(getTenantId(sessionInfo), 1, callback)); } } @@ -419,7 +421,7 @@ public class DefaultTransportService implements TransportService { SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo); sessionMetaData.setSubscribedToRPC(!msg.getUnsubscribe()); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) - .setSubscribeToRPC(msg).build(), callback); + .setSubscribeToRPC(msg).build(), new ApiStatsProxyCallback<>(getTenantId(sessionInfo), 1, callback)); } } @@ -428,7 +430,7 @@ public class DefaultTransportService implements TransportService { if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) - .setToDeviceRPCCallResponse(msg).build(), callback); + .setToDeviceRPCCallResponse(msg).build(), new ApiStatsProxyCallback<>(getTenantId(sessionInfo), 1, callback)); } } @@ -507,8 +509,8 @@ public class DefaultTransportService implements TransportService { sessions.forEach((uuid, sessionMD) -> { long lastActivityTime = sessionMD.getLastActivityTime(); TransportProtos.SessionInfoProto sessionInfo = sessionMD.getSessionInfo(); - if (sessionInfo.getGwSessionIdMSB() > 0 && - sessionInfo.getGwSessionIdLSB() > 0) { + if (sessionInfo.getGwSessionIdMSB() != 0 && + sessionInfo.getGwSessionIdLSB() != 0) { SessionMetaData gwMetaData = sessions.get(new UUID(sessionInfo.getGwSessionIdMSB(), sessionInfo.getGwSessionIdLSB())); if (gwMetaData != null) { lastActivityTime = Math.max(gwMetaData.getLastActivityTime(), lastActivityTime); @@ -642,6 +644,9 @@ public class DefaultTransportService implements TransportService { rateLimitService.update(apiUsageState.getTenantId(), apiUsageState.isTransportEnabled()); //TODO: if transport is disabled, we should close all sessions and not to check credentials. } + } else if (EntityType.DEVICE.equals(entityType)) { + Optional deviceOpt = dataDecodingEncodingService.decode(msg.getData().toByteArray()); + deviceOpt.ifPresent(this::onDeviceUpdate); } } else if (toSessionMsg.hasEntityDeleteMsg()) { TransportProtos.EntityDeleteMsg msg = toSessionMsg.getEntityDeleteMsg(); @@ -667,9 +672,45 @@ public class DefaultTransportService implements TransportService { long deviceProfileIdMSB = deviceProfile.getId().getId().getMostSignificantBits(); long deviceProfileIdLSB = deviceProfile.getId().getId().getLeastSignificantBits(); sessions.forEach((id, md) -> { + //TODO: if transport types are different - we should close the session. if (md.getSessionInfo().getDeviceProfileIdMSB() == deviceProfileIdMSB && md.getSessionInfo().getDeviceProfileIdLSB() == deviceProfileIdLSB) { - transportCallbackExecutor.submit(() -> md.getListener().onProfileUpdate(deviceProfile)); + TransportProtos.SessionInfoProto newSessionInfo = TransportProtos.SessionInfoProto.newBuilder() + .mergeFrom(md.getSessionInfo()) + .setDeviceProfileIdMSB(deviceProfileIdMSB) + .setDeviceProfileIdLSB(deviceProfileIdLSB) + .setDeviceType(deviceProfile.getName()) + .build(); + md.setSessionInfo(newSessionInfo); + transportCallbackExecutor.submit(() -> md.getListener().onDeviceProfileUpdate(newSessionInfo, deviceProfile)); + } + }); + } + + private void onDeviceUpdate(Device device) { + long deviceIdMSB = device.getId().getId().getMostSignificantBits(); + long deviceIdLSB = device.getId().getId().getLeastSignificantBits(); + long deviceProfileIdMSB = device.getDeviceProfileId().getId().getMostSignificantBits(); + long deviceProfileIdLSB = device.getDeviceProfileId().getId().getLeastSignificantBits(); + sessions.forEach((id, md) -> { + if ((md.getSessionInfo().getDeviceIdMSB() == deviceIdMSB && md.getSessionInfo().getDeviceIdLSB() == deviceIdLSB)) { + DeviceProfile newDeviceProfile; + if (md.getSessionInfo().getDeviceProfileIdMSB() != deviceProfileIdMSB + && md.getSessionInfo().getDeviceProfileIdLSB() != deviceProfileIdLSB) { + //TODO: if transport types are different - we should close the session. + newDeviceProfile = deviceProfileCache.get(new DeviceProfileId(new UUID(deviceProfileIdMSB, deviceProfileIdLSB))); + } else { + newDeviceProfile = null; + } + TransportProtos.SessionInfoProto newSessionInfo = TransportProtos.SessionInfoProto.newBuilder() + .mergeFrom(md.getSessionInfo()) + .setDeviceProfileIdMSB(deviceProfileIdMSB) + .setDeviceProfileIdLSB(deviceProfileIdLSB) + .setDeviceName(device.getName()) + .setDeviceType(device.getType()) + .build(); + md.setSessionInfo(newSessionInfo); + transportCallbackExecutor.submit(() -> md.getListener().onDeviceUpdate(newSessionInfo, device, Optional.ofNullable(newDeviceProfile))); } }); } @@ -805,7 +846,7 @@ public class DefaultTransportService implements TransportService { @Override public void onFailure(Throwable t) { - callback.onError(t); + DefaultTransportService.this.transportCallbackExecutor.submit(() -> callback.onError(t)); } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java index 5181d8e781..ac293307a9 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java @@ -27,22 +27,17 @@ import java.util.concurrent.ScheduledFuture; @Data class SessionMetaData { - private final TransportProtos.SessionInfoProto sessionInfo; + private volatile TransportProtos.SessionInfoProto sessionInfo; private final TransportProtos.SessionType sessionType; private final SessionMsgListener listener; - private ScheduledFuture scheduledFuture; - + private volatile ScheduledFuture scheduledFuture; private volatile long lastActivityTime; private volatile long lastReportedActivityTime; private volatile boolean subscribedToAttributes; private volatile boolean subscribedToRPC; - SessionMetaData( - TransportProtos.SessionInfoProto sessionInfo, - TransportProtos.SessionType sessionType, - SessionMsgListener listener - ) { + SessionMetaData(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SessionType sessionType, SessionMsgListener listener) { this.sessionInfo = sessionInfo; this.sessionType = sessionType; this.listener = listener; @@ -54,11 +49,15 @@ class SessionMetaData { this.lastActivityTime = System.currentTimeMillis(); } - void setScheduledFuture(ScheduledFuture scheduledFuture) { this.scheduledFuture = scheduledFuture; } + void setScheduledFuture(ScheduledFuture scheduledFuture) { + this.scheduledFuture = scheduledFuture; + } public ScheduledFuture getScheduledFuture() { return scheduledFuture; } - public boolean hasScheduledFuture() { return null != this.scheduledFuture; } + public boolean hasScheduledFuture() { + return null != this.scheduledFuture; + } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java index 2f7f2d69e6..168a20ba67 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java @@ -18,13 +18,13 @@ package org.thingsboard.server.common.transport.session; import lombok.Data; import lombok.Getter; import lombok.Setter; +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.msg.session.SessionContext; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; +import java.util.Optional; import java.util.UUID; /** @@ -59,10 +59,19 @@ public abstract class DeviceAwareSessionContext implements SessionContext { } @Override - public void onProfileUpdate(DeviceProfile deviceProfile) { + public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) { + this.sessionInfo = sessionInfo; this.deviceProfile = deviceProfile; this.deviceInfo.setDeviceType(deviceProfile.getName()); - this.sessionInfo = TransportProtos.SessionInfoProto.newBuilder().mergeFrom(sessionInfo).setDeviceType(deviceProfile.getName()).build(); + + } + + @Override + public void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional deviceProfileOpt) { + this.sessionInfo = sessionInfo; + this.deviceInfo.setDeviceProfileId(device.getDeviceProfileId()); + this.deviceInfo.setDeviceType(device.getType()); + deviceProfileOpt.ifPresent(profile -> this.deviceProfile = profile); } public boolean isConnected() { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/SessionContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/SessionContext.java new file mode 100644 index 0000000000..fc82974764 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/SessionContext.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2020 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.common.transport.session; + +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.gen.transport.TransportProtos; + +import java.util.Optional; +import java.util.UUID; + +public interface SessionContext { + + UUID getSessionId(); + + int nextMsgId(); + + void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile); + + void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional deviceProfileOpt); +} diff --git a/common/util/pom.xml b/common/util/pom.xml index 076eff152a..474db1270e 100644 --- a/common/util/pom.xml +++ b/common/util/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT common org.thingsboard.common diff --git a/dao/pom.xml b/dao/pom.xml index ec4960eb5f..907ddb7150 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT thingsboard dao diff --git a/dao/src/main/java/org/thingsboard/server/dao/TenantEntityDao.java b/dao/src/main/java/org/thingsboard/server/dao/TenantEntityDao.java new file mode 100644 index 0000000000..e47dd1e301 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/TenantEntityDao.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2020 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.dao; + +import org.thingsboard.server.common.data.id.TenantId; + +public interface TenantEntityDao { + + Long countByTenantId(TenantId tenantId); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java index 1366df3ac2..ae8bf5706c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java @@ -23,6 +23,7 @@ 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.dao.Dao; +import org.thingsboard.server.dao.TenantEntityDao; import java.util.List; import java.util.Optional; @@ -32,7 +33,7 @@ import java.util.UUID; * The Interface AssetDao. * */ -public interface AssetDao extends Dao { +public interface AssetDao extends Dao, TenantEntityDao { /** * Find asset info by id. @@ -166,6 +167,4 @@ public interface AssetDao extends Dao { */ ListenableFuture> findTenantAssetTypesAsync(UUID tenantId); - Long countAssetsByTenantId(TenantId tenantId); - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java index 923baa1df4..e8375d8065 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java @@ -78,6 +78,8 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ public static final String INCORRECT_PAGE_LINK = "Incorrect page link "; public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId "; public static final String INCORRECT_ASSET_ID = "Incorrect assetId "; + public static final String TB_SERVICE_QUEUE = "TbServiceQueue"; + @Autowired private AssetDao assetDao; @@ -329,12 +331,9 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ protected void validateCreate(TenantId tenantId, Asset asset) { DefaultTenantProfileConfiguration profileConfiguration = (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); - long maxAssets = profileConfiguration.getMaxAssets(); - if (maxAssets > 0) { - long currentAssetsCount = assetDao.countAssetsByTenantId(tenantId); - if (maxAssets >= currentAssetsCount) { - throw new DataValidationException("Can't create assets more then " + maxAssets); - } + if (!TB_SERVICE_QUEUE.equals(asset.getType())) { + long maxAssets = profileConfiguration.getMaxAssets(); + validateNumberOfEntitiesPerTenant(tenantId, assetDao, maxAssets, EntityType.ASSET); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java index 8ec43f49a5..458a251f8a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java @@ -16,6 +16,7 @@ package org.thingsboard.server.dao.attributes; import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; @@ -38,4 +39,6 @@ public interface AttributesDao { ListenableFuture save(TenantId tenantId, EntityId entityId, String attributeType, AttributeKvEntry attribute); ListenableFuture> removeAll(TenantId tenantId, EntityId entityId, String attributeType, List keys); + + List findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java index a6924960ca..2e18006036 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java @@ -20,6 +20,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; @@ -59,6 +60,11 @@ public class BaseAttributesService implements AttributesService { return attributesDao.findAll(tenantId, entityId, scope); } + @Override + public List findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId) { + return attributesDao.findAllKeysByDeviceProfileId(tenantId, deviceProfileId); + } + @Override public ListenableFuture> save(TenantId tenantId, EntityId entityId, String scope, List attributes) { validate(entityId, scope); diff --git a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java index 11fa5cb7cd..98622c8b83 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java @@ -20,6 +20,7 @@ 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.dao.Dao; +import org.thingsboard.server.dao.TenantEntityDao; import java.util.Optional; import java.util.UUID; @@ -27,7 +28,7 @@ import java.util.UUID; /** * The Interface CustomerDao. */ -public interface CustomerDao extends Dao { +public interface CustomerDao extends Dao, TenantEntityDao { /** * Save or update customer object @@ -54,5 +55,5 @@ public interface CustomerDao extends Dao { * @return the optional customer object */ Optional findCustomersByTenantIdAndTitle(UUID tenantId, String title); - + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java index 14d591e7e8..d97b4e9c34 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java @@ -21,13 +21,16 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.CustomerId; 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.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.device.DeviceService; @@ -38,6 +41,7 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantDao; import org.thingsboard.server.dao.user.UserService; @@ -75,6 +79,10 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom @Autowired private DashboardService dashboardService; + @Autowired + @Lazy + private TbTenantProfileCache tenantProfileCache; + @Override public Customer findCustomerById(TenantId tenantId, CustomerId customerId) { log.trace("Executing findCustomerById [{}]", customerId); @@ -162,6 +170,11 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom @Override protected void validateCreate(TenantId tenantId, Customer customer) { + DefaultTenantProfileConfiguration profileConfiguration = + (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); + long maxCustomers = profileConfiguration.getMaxCustomers(); + + validateNumberOfEntitiesPerTenant(tenantId, customerDao, maxCustomers, EntityType.CUSTOMER); customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()).ifPresent( c -> { throw new DataValidationException("Customer with such title already exists!"); diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java index 0429c6d469..0beb89ef06 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java @@ -18,11 +18,12 @@ package org.thingsboard.server.dao.dashboard; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.Dao; +import org.thingsboard.server.dao.TenantEntityDao; /** * The Interface DashboardDao. */ -public interface DashboardDao extends Dao { +public interface DashboardDao extends Dao, TenantEntityDao { /** * Save or update dashboard object @@ -31,5 +32,4 @@ public interface DashboardDao extends Dao { * @return saved dashboard object */ Dashboard save(TenantId tenantId, Dashboard dashboard); - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java index 112f998cd4..55bf31e321 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java @@ -19,10 +19,12 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.DashboardInfo; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; @@ -31,12 +33,14 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantDao; import java.util.concurrent.ExecutionException; @@ -61,6 +65,10 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb @Autowired private CustomerDao customerDao; + @Autowired + @Lazy + private TbTenantProfileCache tenantProfileCache; + @Override public Dashboard findDashboardById(TenantId tenantId, DashboardId dashboardId) { log.trace("Executing findDashboardById [{}]", dashboardId); @@ -214,6 +222,14 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb private DataValidator dashboardValidator = new DataValidator() { + @Override + protected void validateCreate(TenantId tenantId, Dashboard data) { + DefaultTenantProfileConfiguration profileConfiguration = + (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); + long maxDashboards = profileConfiguration.getMaxDashboards(); + validateNumberOfEntitiesPerTenant(tenantId, dashboardDao, maxDashboards, EntityType.DASHBOARD); + } + @Override protected void validateDataImpl(TenantId tenantId, Dashboard dashboard) { if (StringUtils.isEmpty(dashboard.getTitle())) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java index 2f321ff499..3e8f6445a2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java @@ -23,6 +23,7 @@ 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.dao.Dao; +import org.thingsboard.server.dao.TenantEntityDao; import java.util.List; import java.util.Optional; @@ -32,7 +33,7 @@ import java.util.UUID; * The Interface DeviceDao. * */ -public interface DeviceDao extends Dao { +public interface DeviceDao extends Dao, TenantEntityDao { /** * Find device info by id. @@ -203,8 +204,6 @@ public interface DeviceDao extends Dao { */ ListenableFuture findDeviceByTenantIdAndIdAsync(TenantId tenantId, UUID id); - Long countDevicesByTenantId(TenantId tenantId); - Long countDevicesByDeviceProfileId(TenantId tenantId, UUID deviceProfileId); /** diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index f3857cacd5..4f19efab26 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -15,6 +15,16 @@ */ package org.thingsboard.server.dao.device; +import com.squareup.wire.Syntax; +import com.squareup.wire.schema.Field; +import com.squareup.wire.schema.Location; +import com.squareup.wire.schema.internal.parser.EnumElement; +import com.squareup.wire.schema.internal.parser.FieldElement; +import com.squareup.wire.schema.internal.parser.MessageElement; +import com.squareup.wire.schema.internal.parser.OneOfElement; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; +import com.squareup.wire.schema.internal.parser.ProtoParser; +import com.squareup.wire.schema.internal.parser.TypeElement; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.hibernate.exception.ConstraintViolationException; @@ -23,18 +33,22 @@ import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileInfo; 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.EntitySubtype; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -48,7 +62,12 @@ import org.thingsboard.server.dao.tenant.TenantDao; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; import static org.thingsboard.server.common.data.CacheConstants.DEVICE_PROFILE_CACHE; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -61,6 +80,14 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D private static final String INCORRECT_DEVICE_PROFILE_ID = "Incorrect deviceProfileId "; private static final String INCORRECT_DEVICE_PROFILE_NAME = "Incorrect deviceProfileName "; + private static final Location LOCATION = new Location("", "", -1, -1); + private static final String ATTRIBUTES_PROTO_SCHEMA = "attributes proto schema"; + private static final String TELEMETRY_PROTO_SCHEMA = "telemetry proto schema"; + + private static String invalidSchemaProvidedMessage(String schemaName) { + return "[Transport Configuration] invalid " + schemaName + " provided!"; + } + @Autowired private DeviceProfileDao deviceProfileDao; @@ -76,6 +103,8 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D @Autowired private CacheManager cacheManager; + private final Lock findOrCreateLock = new ReentrantLock(); + @Cacheable(cacheNames = DEVICE_PROFILE_CACHE, key = "{#deviceProfileId.id}") @Override public DeviceProfile findDeviceProfileById(TenantId tenantId, DeviceProfileId deviceProfileId) { @@ -115,7 +144,7 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_profile_name_unq_key")) { throw new DataValidationException("Device profile with such name already exists!"); } else if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_provision_key_unq_key")) { - throw new DataValidationException("Device profile with such provision device key already exists!"); + throw new DataValidationException("Device profile with such provision device key already exists!"); } else { throw t; } @@ -195,7 +224,15 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D log.trace("Executing findOrCreateDefaultDeviceProfile"); DeviceProfile deviceProfile = findDeviceProfileByName(tenantId, name); if (deviceProfile == null) { - deviceProfile = this.doCreateDefaultDeviceProfile(tenantId, name, name.equals("default")); + try { + findOrCreateLock.lock(); + deviceProfile = findDeviceProfileByName(tenantId, name); + if (deviceProfile == null) { + deviceProfile = this.doCreateDefaultDeviceProfile(tenantId, name, name.equals("default")); + } + } finally { + findOrCreateLock.unlock(); + } } return deviceProfile; } @@ -311,6 +348,37 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D throw new DataValidationException("Another default device profile is present in scope of current tenant!"); } } + + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + if (transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) { + MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + if (mqttTransportConfiguration.getTransportPayloadTypeConfiguration() instanceof ProtoTransportPayloadConfiguration) { + ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration = + (ProtoTransportPayloadConfiguration) mqttTransportConfiguration.getTransportPayloadTypeConfiguration(); + try { + validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceAttributesProtoSchema(), ATTRIBUTES_PROTO_SCHEMA); + validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceTelemetryProtoSchema(), TELEMETRY_PROTO_SCHEMA); + } catch (Exception exception) { + throw new DataValidationException(exception.getMessage()); + } + } + } + + List profileAlarms = deviceProfile.getProfileData().getAlarms(); + + if (!CollectionUtils.isEmpty(profileAlarms)) { + Set alarmTypes = new HashSet<>(); + for (DeviceProfileAlarm alarm : profileAlarms) { + String alarmType = alarm.getAlarmType(); + if (StringUtils.isEmpty(alarmType)) { + throw new DataValidationException("Alarm rule type should be specified!"); + } + if (!alarmTypes.add(alarmType)) { + throw new DataValidationException(String.format("Can't create device profile with the same alarm rule types: \"%s\"!", alarmType)); + } + } + } + } @Override @@ -334,6 +402,122 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D } } } + + private void validateTransportProtoSchema(String schema, String schemaName) throws IllegalArgumentException { + ProtoParser schemaParser = new ProtoParser(LOCATION, schema.toCharArray()); + ProtoFileElement protoFileElement; + try { + protoFileElement = schemaParser.readProtoFile(); + } catch (Exception e) { + throw new IllegalArgumentException("[Transport Configuration] failed to parse " + schemaName + " due to: " + e.getMessage()); + } + checkProtoFileSyntax(schemaName, protoFileElement); + checkProtoFileCommonSettings(schemaName, protoFileElement.getOptions().isEmpty(), " Schema options don't support!"); + checkProtoFileCommonSettings(schemaName, protoFileElement.getPublicImports().isEmpty(), " Schema public imports don't support!"); + checkProtoFileCommonSettings(schemaName, protoFileElement.getImports().isEmpty(), " Schema imports don't support!"); + checkProtoFileCommonSettings(schemaName, protoFileElement.getExtendDeclarations().isEmpty(), " Schema extend declarations don't support!"); + checkTypeElements(schemaName, protoFileElement); + } + + private void checkProtoFileSyntax(String schemaName, ProtoFileElement protoFileElement) { + if (protoFileElement.getSyntax() == null || !protoFileElement.getSyntax().equals(Syntax.PROTO_3)) { + throw new IllegalArgumentException("[Transport Configuration] invalid schema syntax: " + protoFileElement.getSyntax() + + " for " + schemaName + " provided! Only " + Syntax.PROTO_3 + " allowed!"); + } + } + + private void checkProtoFileCommonSettings(String schemaName, boolean isEmptySettings, String invalidSettingsMessage) { + if (!isEmptySettings) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + invalidSettingsMessage); + } + } + + private void checkTypeElements(String schemaName, ProtoFileElement protoFileElement) { + List types = protoFileElement.getTypes(); + if (!types.isEmpty()) { + if (types.stream().noneMatch(typeElement -> typeElement instanceof MessageElement)) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " At least one Message definition should exists!"); + } else { + checkEnumElements(schemaName, getEnumElements(types)); + checkMessageElements(schemaName, getMessageTypes(types)); + } + } else { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Type elements is empty!"); + } + } + + private void checkFieldElements(String schemaName, List fieldElements) { + if (!fieldElements.isEmpty()) { + boolean hasRequiredLabel = fieldElements.stream().anyMatch(fieldElement -> { + Field.Label label = fieldElement.getLabel(); + return label != null && label.equals(Field.Label.REQUIRED); + }); + if (hasRequiredLabel) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Required labels are not supported!"); + } + boolean hasDefaultValue = fieldElements.stream().anyMatch(fieldElement -> fieldElement.getDefaultValue() != null); + if (hasDefaultValue) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Default values are not supported!"); + } + } + } + + private void checkEnumElements(String schemaName, List enumTypes) { + if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getNestedTypes().isEmpty())) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Nested types in Enum definitions are not supported!"); + } + if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getOptions().isEmpty())) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Enum definitions options are not supported!"); + } + } + + private void checkMessageElements(String schemaName, List messageElementsList) { + if (!messageElementsList.isEmpty()) { + messageElementsList.forEach(messageElement -> { + checkProtoFileCommonSettings(schemaName, messageElement.getGroups().isEmpty(), + " Message definition groups don't support!"); + checkProtoFileCommonSettings(schemaName, messageElement.getOptions().isEmpty(), + " Message definition options don't support!"); + checkProtoFileCommonSettings(schemaName, messageElement.getExtensions().isEmpty(), + " Message definition extensions don't support!"); + checkProtoFileCommonSettings(schemaName, messageElement.getReserveds().isEmpty(), + " Message definition reserved elements don't support!"); + checkFieldElements(schemaName, messageElement.getFields()); + List oneOfs = messageElement.getOneOfs(); + if (!oneOfs.isEmpty()) { + oneOfs.forEach(oneOfElement -> { + checkProtoFileCommonSettings(schemaName, oneOfElement.getGroups().isEmpty(), + " OneOf definition groups don't support!"); + checkFieldElements(schemaName, oneOfElement.getFields()); + }); + } + List nestedTypes = messageElement.getNestedTypes(); + if (!nestedTypes.isEmpty()) { + List nestedEnumTypes = getEnumElements(nestedTypes); + if (!nestedEnumTypes.isEmpty()) { + checkEnumElements(schemaName, nestedEnumTypes); + } + List nestedMessageTypes = getMessageTypes(nestedTypes); + checkMessageElements(schemaName, nestedMessageTypes); + } + }); + } + } + + private List getMessageTypes(List types) { + return types.stream() + .filter(typeElement -> typeElement instanceof MessageElement) + .map(typeElement -> (MessageElement) typeElement) + .collect(Collectors.toList()); + } + + private List getEnumElements(List types) { + return types.stream() + .filter(typeElement -> typeElement instanceof EnumElement) + .map(typeElement -> (EnumElement) typeElement) + .collect(Collectors.toList()); + } + }; private PaginatedRemover tenantDeviceProfilesRemover = diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index 24c597b4df..a919f86dfe 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -530,12 +530,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe DefaultTenantProfileConfiguration profileConfiguration = (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); long maxDevices = profileConfiguration.getMaxDevices(); - if (maxDevices > 0) { - long currentDevicesCount = deviceDao.countDevicesByTenantId(tenantId); - if (maxDevices >= currentDevicesCount) { - throw new DataValidationException("Can't create devices more then " + maxDevices); - } - } + validateNumberOfEntitiesPerTenant(tenantId, deviceDao, maxDevices, EntityType.DEVICE); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java index a0f03b2a32..42d02addcd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java @@ -37,12 +37,11 @@ public abstract class AbstractEntityService { protected Optional extractConstraintViolationException(Exception t) { if (t instanceof ConstraintViolationException) { - return Optional.of ((ConstraintViolationException) t); + return Optional.of((ConstraintViolationException) t); } else if (t.getCause() instanceof ConstraintViolationException) { - return Optional.of ((ConstraintViolationException) (t.getCause())); + return Optional.of((ConstraintViolationException) (t.getCause())); } else { return Optional.empty(); } } - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index e863de1edc..e55f55eef2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -450,6 +450,8 @@ public class ModelConstants { public static final String API_USAGE_STATE_DB_STORAGE_COLUMN = "db_storage"; public static final String API_USAGE_STATE_RE_EXEC_COLUMN = "re_exec"; public static final String API_USAGE_STATE_JS_EXEC_COLUMN = "js_exec"; + public static final String API_USAGE_STATE_EMAIL_EXEC_COLUMN = "email_exec"; + public static final String API_USAGE_STATE_SMS_EXEC_COLUMN = "sms_exec"; /** * Cassandra attributes and timeseries constants. diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java index bc4aed6398..4b07d02321 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java @@ -63,6 +63,12 @@ public class ApiUsageStateEntity extends BaseSqlEntity implements @Enumerated(EnumType.STRING) @Column(name = ModelConstants.API_USAGE_STATE_JS_EXEC_COLUMN) private ApiUsageStateValue jsExecState = ApiUsageStateValue.ENABLED; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.API_USAGE_STATE_EMAIL_EXEC_COLUMN) + private ApiUsageStateValue emailExecState = ApiUsageStateValue.ENABLED; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.API_USAGE_STATE_SMS_EXEC_COLUMN) + private ApiUsageStateValue smsExecState = ApiUsageStateValue.ENABLED; public ApiUsageStateEntity() { } @@ -83,6 +89,8 @@ public class ApiUsageStateEntity extends BaseSqlEntity implements this.dbStorageState = ur.getDbStorageState(); this.reExecState = ur.getReExecState(); this.jsExecState = ur.getJsExecState(); + this.emailExecState = ur.getEmailExecState(); + this.smsExecState = ur.getSmsExecState(); } @Override @@ -99,6 +107,8 @@ public class ApiUsageStateEntity extends BaseSqlEntity implements ur.setDbStorageState(dbStorageState); ur.setReExecState(reExecState); ur.setJsExecState(jsExecState); + ur.setEmailExecState(emailExecState); + ur.setSmsExecState(smsExecState); return ur; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index 5997839119..263f01570e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -24,6 +24,7 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.EntityType; @@ -44,11 +45,13 @@ import org.thingsboard.server.common.data.rule.RuleChainData; import org.thingsboard.server.common.data.rule.RuleChainImportResult; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantDao; import java.util.ArrayList; @@ -81,6 +84,10 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC @Autowired private TenantDao tenantDao; + @Autowired + @Lazy + private TbTenantProfileCache tenantProfileCache; + @Override public RuleChain saveRuleChain(RuleChain ruleChain) { ruleChainValidator.validate(ruleChain, RuleChain::getTenantId); @@ -580,6 +587,14 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC private DataValidator ruleChainValidator = new DataValidator() { + @Override + protected void validateCreate(TenantId tenantId, RuleChain data) { + DefaultTenantProfileConfiguration profileConfiguration = + (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); + long maxRuleChains = profileConfiguration.getMaxRuleChains(); + validateNumberOfEntitiesPerTenant(tenantId, ruleChainDao, maxRuleChains, EntityType.RULE_CHAIN); + } + @Override protected void validateDataImpl(TenantId tenantId, RuleChain ruleChain) { if (StringUtils.isEmpty(ruleChain.getName())) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java index c3214425fe..87ab9d26b6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java @@ -19,13 +19,14 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.dao.Dao; +import org.thingsboard.server.dao.TenantEntityDao; import java.util.UUID; /** * Created by igor on 3/12/18. */ -public interface RuleChainDao extends Dao { +public interface RuleChainDao extends Dao, TenantEntityDao { /** * Find rule chains by tenantId and page link. @@ -35,5 +36,4 @@ public interface RuleChainDao extends Dao { * @return the list of rule chain objects */ PageData findRuleChainsByTenantId(UUID tenantId, PageLink pageLink); - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java index d78654164f..0bd0e7b030 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java @@ -18,7 +18,9 @@ package org.thingsboard.server.dao.service; import com.fasterxml.jackson.databind.JsonNode; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.TenantEntityDao; import org.thingsboard.server.dao.exception.DataValidationException; import java.util.HashSet; @@ -79,6 +81,19 @@ public abstract class DataValidator> { return emailMatcher.matches(); } + protected void validateNumberOfEntitiesPerTenant(TenantId tenantId, + TenantEntityDao tenantEntityDao, + long maxEntities, + EntityType entityType) { + if (maxEntities > 0) { + long currentEntitiesCount = tenantEntityDao.countByTenantId(tenantId); + if (currentEntitiesCount >= maxEntities) { + throw new DataValidationException(String.format("Can't create more then %d %ss!", + maxEntities, entityType.name().toLowerCase().replaceAll("_", " "))); + } + } + } + protected static void validateJsonStructure(JsonNode expectedNode, JsonNode actualNode) { Set expectedFields = new HashSet<>(); Iterator fieldsIterator = expectedNode.fieldNames(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java index a6e016c418..da2bc9e687 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java @@ -122,5 +122,5 @@ public interface AssetRepository extends PagingAndSortingRepository findTenantAssetTypes(@Param("tenantId") UUID tenantId); - Long countByTenantId(UUID tenantId); + Long countByTenantIdAndTypeIsNot(UUID tenantId, String type); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java index 698183ea0a..b56a3eb3a9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java @@ -39,6 +39,8 @@ import java.util.Objects; import java.util.Optional; import java.util.UUID; +import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE; + /** * Created by Valerii Sosliuk on 5/19/2017. */ @@ -178,8 +180,7 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao im } @Override - public Long countAssetsByTenantId(TenantId tenantId) { - return assetRepository.countByTenantId(tenantId.getId()); - + public Long countByTenantId(TenantId tenantId) { + return assetRepository.countByTenantIdAndTypeIsNot(tenantId.getId(), TB_SERVICE_QUEUE); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java index f3ef44e6c7..f6c3e195e7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java @@ -46,5 +46,15 @@ public interface AttributeKvRepository extends CrudRepository findAllKeysByDeviceProfileId(@Param("tenantId") UUID tenantId, + @Param("deviceProfileId") UUID deviceProfileId); + + @Query(value = "SELECT DISTINCT attribute_key FROM attribute_kv WHERE entity_type = 'DEVICE' " + + "AND entity_id in (SELECT id FROM device WHERE tenant_id = :tenantId limit 100) ORDER BY attribute_key", nativeQuery = true) + List findAllKeysByTenantId(@Param("tenantId") UUID tenantId); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java index 69616155d2..a95bd4b612 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java @@ -22,6 +22,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; @@ -135,6 +136,15 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl attributeType)))); } + @Override + public List findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId) { + if (deviceProfileId != null) { + return attributeKvRepository.findAllKeysByDeviceProfileId(tenantId.getId(), deviceProfileId.getId()); + } else { + return attributeKvRepository.findAllKeysByTenantId(tenantId.getId()); + } + } + @Override public ListenableFuture save(TenantId tenantId, EntityId entityId, String attributeType, AttributeKvEntry attribute) { AttributeKvEntity entity = new AttributeKvEntity(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/customer/CustomerRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/customer/CustomerRepository.java index bf2a845ec0..4b5a8e0791 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/customer/CustomerRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/customer/CustomerRepository.java @@ -37,4 +37,5 @@ public interface CustomerRepository extends PagingAndSortingRepository { + + Long countByTenantId(UUID tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java index 8d637f0cbe..0f083cf6b9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java @@ -19,6 +19,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Dashboard; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.dashboard.DashboardDao; import org.thingsboard.server.dao.model.sql.DashboardEntity; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; @@ -43,4 +44,9 @@ public class JpaDashboardDao extends JpaAbstractSearchTextDao getCrudRepository() { return dashboardRepository; } + + @Override + public Long countByTenantId(TenantId tenantId) { + return dashboardRepository.countByTenantId(tenantId.getId()); + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java index 9f3cc5b5a7..9519b169e2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java @@ -50,9 +50,9 @@ public interface DeviceRepository extends PagingAndSortingRepository findByTenantIdAndProfileId(@Param("tenantId") UUID tenantId, - @Param("profileId") UUID profileId, - @Param("searchText") String searchText, - Pageable pageable); + @Param("profileId") UUID profileId, + @Param("searchText") String searchText, + Pageable pageable); @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo, p.name) " + "FROM DeviceEntity d " + @@ -62,9 +62,9 @@ public interface DeviceRepository extends PagingAndSortingRepository findDeviceInfosByTenantIdAndCustomerId(@Param("tenantId") UUID tenantId, - @Param("customerId") UUID customerId, - @Param("searchText") String searchText, - Pageable pageable); + @Param("customerId") UUID customerId, + @Param("searchText") String searchText, + Pageable pageable); @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId") Page findByTenantId(@Param("tenantId") UUID tenantId, @@ -102,9 +102,9 @@ public interface DeviceRepository extends PagingAndSortingRepository findDeviceInfosByTenantIdAndType(@Param("tenantId") UUID tenantId, - @Param("type") String type, - @Param("textSearch") String textSearch, - Pageable pageable); + @Param("type") String type, + @Param("textSearch") String textSearch, + Pageable pageable); @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo, p.name) " + "FROM DeviceEntity d " + @@ -137,10 +137,10 @@ public interface DeviceRepository extends PagingAndSortingRepository findDeviceInfosByTenantIdAndCustomerIdAndType(@Param("tenantId") UUID tenantId, - @Param("customerId") UUID customerId, - @Param("type") String type, - @Param("textSearch") String textSearch, - Pageable pageable); + @Param("customerId") UUID customerId, + @Param("type") String type, + @Param("textSearch") String textSearch, + Pageable pageable); @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo, p.name) " + "FROM DeviceEntity d " + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java index fceefcadf9..7ce59bda0d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java @@ -220,7 +220,7 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao } @Override - public Long countDevicesByTenantId(TenantId tenantId) { + public Long countByTenantId(TenantId tenantId) { return deviceRepository.countByTenantId(tenantId.getId()); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java index e3aa614bfe..c1a419381a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java @@ -72,8 +72,8 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { alarmFieldColumnMap.put("status", ModelConstants.ALARM_STATUS_PROPERTY); alarmFieldColumnMap.put("type", ModelConstants.ALARM_TYPE_PROPERTY); alarmFieldColumnMap.put("severity", ModelConstants.ALARM_SEVERITY_PROPERTY); - alarmFieldColumnMap.put("originator_id", ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY); - alarmFieldColumnMap.put("originator_type", ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY); + alarmFieldColumnMap.put("originatorId", ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY); + alarmFieldColumnMap.put("originatorType", ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY); alarmFieldColumnMap.put("originator", "originator_name"); } @@ -130,7 +130,6 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { AlarmDataPageLink pageLink = query.getPageLink(); QueryContext ctx = new QueryContext(new QuerySecurityContext(tenantId, customerId, EntityType.ALARM)); ctx.addUuidListParameter("entity_ids", orderedEntityIds.stream().map(EntityId::getId).collect(Collectors.toList())); - StringBuilder selectPart = new StringBuilder(FIELDS_SELECTION); StringBuilder fromPart = new StringBuilder(" from alarm a "); StringBuilder wherePart = new StringBuilder(" where "); @@ -157,7 +156,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { wherePart.append(" a.originator_id in (:entity_ids)"); } } else { - fromPart.append(" left join (select * from (VALUES"); + fromPart.append(" inner join (select * from (VALUES"); int entityIdIdx = 0; int lastEntityIdIdx = orderedEntityIds.size() - 1; for (EntityId entityId : orderedEntityIds) { @@ -282,24 +281,27 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { StringBuilder permissionsQuery = new StringBuilder(); ctx.addUuidParameter("permissions_tenant_id", tenantId.getId()); permissionsQuery.append(" a.tenant_id = :permissions_tenant_id "); - if (customerId != null && !customerId.isNullUid()) { - ctx.addUuidParameter("permissions_customer_id", customerId.getId()); - ctx.addUuidParameter("permissions_device_customer_id", customerId.getId()); - ctx.addUuidParameter("permissions_asset_customer_id", customerId.getId()); - ctx.addUuidParameter("permissions_user_customer_id", customerId.getId()); - ctx.addUuidParameter("permissions_entity_view_customer_id", customerId.getId()); - permissionsQuery.append(" and ("); - permissionsQuery.append("(a.originator_type = '").append(EntityType.DEVICE.ordinal()).append("' and exists (select 1 from device cd where cd.id = a.originator_id and cd.customer_id = :permissions_device_customer_id))"); - permissionsQuery.append(" or "); - permissionsQuery.append("(a.originator_type = '").append(EntityType.ASSET.ordinal()).append("' and exists (select 1 from asset ca where ca.id = a.originator_id and ca.customer_id = :permissions_device_customer_id))"); - permissionsQuery.append(" or "); - permissionsQuery.append("(a.originator_type = '").append(EntityType.CUSTOMER.ordinal()).append("' and exists (select 1 from customer cc where cc.id = a.originator_id and cc.id = :permissions_customer_id))"); - permissionsQuery.append(" or "); - permissionsQuery.append("(a.originator_type = '").append(EntityType.USER.ordinal()).append("' and exists (select 1 from tb_user cu where cu.id = a.originator_id and cu.customer_id = :permissions_user_customer_id))"); - permissionsQuery.append(" or "); - permissionsQuery.append("(a.originator_type = '").append(EntityType.ENTITY_VIEW.ordinal()).append("' and exists (select 1 from entity_view cv where cv.id = a.originator_id and cv.customer_id = :permissions_entity_view_customer_id))"); - permissionsQuery.append(")"); - } +/* + No need to check the customer id, because we already use entity id list that passed security check when we were evaluating the data query. + */ +// if (customerId != null && !customerId.isNullUid()) { +// ctx.addUuidParameter("permissions_customer_id", customerId.getId()); +// ctx.addUuidParameter("permissions_device_customer_id", customerId.getId()); +// ctx.addUuidParameter("permissions_asset_customer_id", customerId.getId()); +// ctx.addUuidParameter("permissions_user_customer_id", customerId.getId()); +// ctx.addUuidParameter("permissions_entity_view_customer_id", customerId.getId()); +// permissionsQuery.append(" and ("); +// permissionsQuery.append("(a.originator_type = '").append(EntityType.DEVICE.ordinal()).append("' and exists (select 1 from device cd where cd.id = a.originator_id and cd.customer_id = :permissions_device_customer_id))"); +// permissionsQuery.append(" or "); +// permissionsQuery.append("(a.originator_type = '").append(EntityType.ASSET.ordinal()).append("' and exists (select 1 from asset ca where ca.id = a.originator_id and ca.customer_id = :permissions_device_customer_id))"); +// permissionsQuery.append(" or "); +// permissionsQuery.append("(a.originator_type = '").append(EntityType.CUSTOMER.ordinal()).append("' and exists (select 1 from customer cc where cc.id = a.originator_id and cc.id = :permissions_customer_id))"); +// permissionsQuery.append(" or "); +// permissionsQuery.append("(a.originator_type = '").append(EntityType.USER.ordinal()).append("' and exists (select 1 from tb_user cu where cu.id = a.originator_id and cu.customer_id = :permissions_user_customer_id))"); +// permissionsQuery.append(" or "); +// permissionsQuery.append("(a.originator_type = '").append(EntityType.ENTITY_VIEW.ordinal()).append("' and exists (select 1 from entity_view cv where cv.id = a.originator_id and cv.customer_id = :permissions_entity_view_customer_id))"); +// permissionsQuery.append(")"); +// } return permissionsQuery.toString(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java index 57d1545a94..3331f57e00 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; +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.common.data.rule.RuleChain; @@ -56,4 +57,8 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao imple DaoUtil.toPageable(pageLink))); } + + @Override + public Long countByTenantId(TenantId tenantId) { + return userRepository.countByTenantId(tenantId.getId()); + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java index 1d512a7944..ceaf09ec59 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java @@ -47,4 +47,5 @@ public interface UserRepository extends PagingAndSortingRepository findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId) { + if (deviceProfileId != null) { + return tsKvLatestRepository.getKeysByDeviceProfileId(tenantId.getId(), deviceProfileId.getId()); + } else { + return tsKvLatestRepository.getKeysByTenantId(tenantId.getId()); + } + } + private ListenableFuture getNewLatestEntryFuture(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { ListenableFuture> future = findNewLatestEntryFuture(tenantId, entityId, query); return Futures.transformAsync(future, entryList -> { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/TsKvLatestRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/TsKvLatestRepository.java index bb53106254..cd3db69d70 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/TsKvLatestRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/TsKvLatestRepository.java @@ -15,10 +15,25 @@ */ package org.thingsboard.server.dao.sqlts.latest; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestCompositeKey; import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity; +import java.util.List; +import java.util.UUID; + public interface TsKvLatestRepository extends CrudRepository { + @Query(value = "SELECT DISTINCT ts_kv_dictionary.key AS strKey FROM ts_kv_latest " + + "INNER JOIN ts_kv_dictionary ON ts_kv_latest.key = ts_kv_dictionary.key_id " + + "WHERE ts_kv_latest.entity_id IN (SELECT id FROM device WHERE device_profile_id = :device_profile_id AND tenant_id = :tenant_id limit 100) ORDER BY ts_kv_dictionary.key", nativeQuery = true) + List getKeysByDeviceProfileId(@Param("tenant_id") UUID tenantId, @Param("device_profile_id") UUID deviceProfileId); + + @Query(value = "SELECT DISTINCT ts_kv_dictionary.key AS strKey FROM ts_kv_latest " + + "INNER JOIN ts_kv_dictionary ON ts_kv_latest.key = ts_kv_dictionary.key_id " + + "WHERE ts_kv_latest.entity_id IN (SELECT id FROM device WHERE tenant_id = :tenant_id limit 100) ORDER BY ts_kv_dictionary.key", nativeQuery = true) + List getKeysByTenantId(@Param("tenant_id") UUID tenantId); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/DefaultTbTenantProfileCache.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/DefaultTbTenantProfileCache.java index adf05e0f74..b468019872 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/DefaultTbTenantProfileCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/DefaultTbTenantProfileCache.java @@ -19,16 +19,15 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; -import org.thingsboard.server.dao.tenant.TbTenantProfileCache; -import org.thingsboard.server.dao.tenant.TenantProfileService; -import org.thingsboard.server.dao.tenant.TenantService; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; @Service @Slf4j @@ -40,6 +39,7 @@ public class DefaultTbTenantProfileCache implements TbTenantProfileCache { private final ConcurrentMap tenantProfilesMap = new ConcurrentHashMap<>(); private final ConcurrentMap tenantsMap = new ConcurrentHashMap<>(); + private final ConcurrentMap>> profileListeners = new ConcurrentHashMap<>(); public DefaultTbTenantProfileCache(TenantProfileService tenantProfileService, TenantService tenantService) { this.tenantProfileService = tenantProfileService; @@ -85,17 +85,56 @@ public class DefaultTbTenantProfileCache implements TbTenantProfileCache { public void put(TenantProfile profile) { if (profile.getId() != null) { tenantProfilesMap.put(profile.getId(), profile); + notifyTenantListeners(profile); } } @Override public void evict(TenantProfileId profileId) { tenantProfilesMap.remove(profileId); + notifyTenantListeners(get(profileId)); + } + + public void notifyTenantListeners(TenantProfile tenantProfile) { + if (tenantProfile != null) { + tenantsMap.forEach(((tenantId, tenantProfileId) -> { + if (tenantProfileId.equals(tenantProfile.getId())) { + ConcurrentMap> tenantListeners = profileListeners.get(tenantId); + if (tenantListeners != null) { + tenantListeners.forEach((id, listener) -> listener.accept(tenantProfile)); + } + } + })); + } } @Override public void evict(TenantId tenantId) { tenantsMap.remove(tenantId); + TenantProfile tenantProfile = get(tenantId); + if (tenantProfile != null) { + ConcurrentMap> tenantListeners = profileListeners.get(tenantId); + if (tenantListeners != null) { + tenantListeners.forEach((id, listener) -> listener.accept(tenantProfile)); + } + } + } + + @Override + public void addListener(TenantId tenantId, EntityId listenerId, Consumer profileListener) { + //Force cache of the tenant id. + get(tenantId); + if (profileListener != null) { + profileListeners.computeIfAbsent(tenantId, id -> new ConcurrentHashMap<>()).put(listenerId, profileListener); + } + } + + @Override + public void removeListener(TenantId tenantId, EntityId listenerId) { + ConcurrentMap> tenantListeners = profileListeners.get(tenantId); + if (tenantListeners != null) { + tenantListeners.remove(listenerId); + } } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java index 3b97e2bb82..7160baf939 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java @@ -27,6 +27,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; @@ -40,7 +41,6 @@ import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.Validator; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -116,6 +116,11 @@ public class BaseTimeseriesService implements TimeseriesService { return timeseriesLatestDao.findAllLatest(tenantId, entityId); } + @Override + public List findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId) { + return timeseriesLatestDao.findAllKeysByDeviceProfileId(tenantId, deviceProfileId); + } + @Override public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) { validate(entityId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesLatestDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesLatestDao.java index 27842dcf18..f086f913c0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesLatestDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesLatestDao.java @@ -18,7 +18,6 @@ package org.thingsboard.server.dao.timeseries; import com.datastax.oss.driver.api.core.cql.BoundStatement; import com.datastax.oss.driver.api.core.cql.BoundStatementBuilder; import com.datastax.oss.driver.api.core.cql.PreparedStatement; -import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.querybuilder.QueryBuilder; import com.google.common.util.concurrent.FutureCallback; @@ -28,14 +27,13 @@ import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.Aggregation; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; -import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; -import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.nosql.TbResultSet; @@ -43,6 +41,7 @@ import org.thingsboard.server.dao.sqlts.AggregationTimeseriesDao; import org.thingsboard.server.dao.util.NoSqlTsLatestDao; import javax.annotation.Nullable; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.concurrent.ExecutionException; @@ -82,6 +81,11 @@ public class CassandraBaseTimeseriesLatestDao extends AbstractCassandraBaseTimes return getFutureAsync(executeAsyncRead(tenantId, stmt), rs -> convertAsyncResultSetToTsKvEntryList(rs)); } + @Override + public List findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId) { + return Collections.emptyList(); + } + @Override public ListenableFuture saveLatest(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) { BoundStatementBuilder stmtBuilder = new BoundStatementBuilder(getLatestStmt().bind()); diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesLatestDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesLatestDao.java index 7392e0e3d4..0b7156e03e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesLatestDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesLatestDao.java @@ -16,6 +16,7 @@ package org.thingsboard.server.dao.timeseries; import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; @@ -33,4 +34,5 @@ public interface TimeseriesLatestDao { ListenableFuture removeLatest(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query); + List findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java index 7836154c62..5677b0d73b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java @@ -78,6 +78,8 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A apiUsageState.setReExecState(ApiUsageStateValue.ENABLED); apiUsageState.setJsExecState(ApiUsageStateValue.ENABLED); apiUsageState.setDbStorageState(ApiUsageStateValue.ENABLED); + apiUsageState.setSmsExecState(ApiUsageStateValue.ENABLED); + apiUsageState.setEmailExecState(ApiUsageStateValue.ENABLED); apiUsageStateValidator.validate(apiUsageState, ApiUsageState::getTenantId); ApiUsageState saved = apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState); @@ -94,6 +96,10 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A new StringDataEntry(ApiFeature.RE.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), new StringDataEntry(ApiFeature.JS.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); + apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), + new StringDataEntry(ApiFeature.EMAIL.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); + apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), + new StringDataEntry(ApiFeature.SMS.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); tsService.save(tenantId, saved.getId(), apiUsageStates, 0L); List profileThresholds = new ArrayList<>(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java index 12bebe9cec..4cffadd595 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java @@ -20,10 +20,11 @@ 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.dao.Dao; +import org.thingsboard.server.dao.TenantEntityDao; import java.util.UUID; -public interface UserDao extends Dao { +public interface UserDao extends Dao, TenantEntityDao { /** * Save or update user object @@ -49,7 +50,7 @@ public interface UserDao extends Dao { * @return the list of user entities */ PageData findByTenantId(UUID tenantId, PageLink pageLink); - + /** * Find tenant admin users by tenantId and page link. * @@ -58,7 +59,7 @@ public interface UserDao extends Dao { * @return the list of user entities */ PageData findTenantAdmins(UUID tenantId, PageLink pageLink); - + /** * Find customer users by tenantId, customerId and page link. * @@ -68,5 +69,4 @@ public interface UserDao extends Dao { * @return the list of user entities */ PageData findCustomerUsers(UUID tenantId, UUID customerId, PageLink pageLink); - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index 314684c3fe..a19ec57f48 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java @@ -24,8 +24,10 @@ import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.CustomerId; @@ -36,6 +38,7 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.UserCredentials; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; @@ -43,6 +46,7 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantDao; import java.util.HashMap; @@ -84,6 +88,10 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic @Autowired private CustomerDao customerDao; + @Autowired + @Lazy + private TbTenantProfileCache tenantProfileCache; + @Override public User findUserByEmail(TenantId tenantId, String email) { log.trace("Executing findUserByEmail [{}]", email); @@ -364,6 +372,16 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic private DataValidator userValidator = new DataValidator() { + @Override + protected void validateCreate(TenantId tenantId, User user) { + if (!user.getTenantId().getId().equals(ModelConstants.NULL_UUID)) { + DefaultTenantProfileConfiguration profileConfiguration = + (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); + long maxUsers = profileConfiguration.getMaxUsers(); + validateNumberOfEntitiesPerTenant(tenantId, userDao, maxUsers, EntityType.USER); + } + } + @Override protected void validateDataImpl(TenantId requestTenantId, User user) { if (StringUtils.isEmpty(user.getEmail())) { diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index 0e8f8a7791..feeef778ec 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -114,7 +114,7 @@ CREATE TABLE IF NOT EXISTS customer ( CREATE TABLE IF NOT EXISTS dashboard ( id uuid NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY, created_time bigint NOT NULL, - configuration varchar(10000000), + configuration varchar, assigned_customers varchar(1000000), search_text varchar(255), tenant_id uuid, @@ -416,5 +416,7 @@ CREATE TABLE IF NOT EXISTS api_usage_state ( db_storage varchar(32), re_exec varchar(32), js_exec varchar(32), + email_exec varchar(32), + sms_exec varchar(32), CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id) ); diff --git a/dao/src/main/resources/sql/schema-entities-idx.sql b/dao/src/main/resources/sql/schema-entities-idx.sql index 97134a8e19..b610c99803 100644 --- a/dao/src/main/resources/sql/schema-entities-idx.sql +++ b/dao/src/main/resources/sql/schema-entities-idx.sql @@ -34,6 +34,8 @@ CREATE INDEX IF NOT EXISTS idx_device_customer_id_and_type ON device(tenant_id, CREATE INDEX IF NOT EXISTS idx_device_type ON device(tenant_id, type); +CREATE INDEX IF NOT EXISTS idx_device_device_profile_id ON device(tenant_id, device_profile_id); + CREATE INDEX IF NOT EXISTS idx_asset_customer_id ON asset(tenant_id, customer_id); CREATE INDEX IF NOT EXISTS idx_asset_customer_id_and_type ON asset(tenant_id, customer_id, type); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index db0362a877..67e0bb4141 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -132,7 +132,7 @@ CREATE TABLE IF NOT EXISTS customer ( CREATE TABLE IF NOT EXISTS dashboard ( id uuid NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY, created_time bigint NOT NULL, - configuration varchar(10000000), + configuration varchar, assigned_customers varchar(1000000), search_text varchar(255), tenant_id uuid, @@ -442,6 +442,8 @@ CREATE TABLE IF NOT EXISTS api_usage_state ( db_storage varchar(32), re_exec varchar(32), js_exec varchar(32), + email_exec varchar(32), + sms_exec varchar(32), CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id) ); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java index 15bd4eb905..16e895a869 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; +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; @@ -45,6 +46,7 @@ import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.dao.alarm.AlarmOperationResult; +import org.thingsboard.server.dao.util.mapping.JacksonUtil; import java.util.Arrays; import java.util.Collections; @@ -265,7 +267,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { PageData tenantAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, new CustomerId(CustomerId.NULL_UUID), toQuery(pageLink), Arrays.asList(tenantDevice.getId(), customerDevice.getId())); Assert.assertEquals(2, tenantAlarms.getData().size()); - PageData customerAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, customer.getId(), toQuery(pageLink), Arrays.asList(tenantDevice.getId(), customerDevice.getId())); + PageData customerAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, customer.getId(), toQuery(pageLink), Collections.singletonList(customerDevice.getId())); Assert.assertEquals(1, customerAlarms.getData().size()); Assert.assertEquals(deviceAlarm, customerAlarms.getData().get(0)); @@ -280,6 +282,70 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { Assert.assertEquals(tenantAlarm, alarms.getData().get(0)); } + @Test + public void testFindPopagatedCustomerAlarm() throws ExecutionException, InterruptedException { + Customer customer = new Customer(); + customer.setTitle("TestCustomer"); + customer.setTenantId(tenantId); + customer = customerService.saveCustomer(customer); + + Device tenantDevice = new Device(); + tenantDevice.setName("TestTenantDevice"); + tenantDevice.setType("default"); + tenantDevice.setTenantId(tenantId); + tenantDevice = deviceService.saveDevice(tenantDevice); + + Asset customerAsset = new Asset(); + customerAsset.setName("TestCustomerDevice"); + customerAsset.setType("default"); + customerAsset.setTenantId(tenantId); + customerAsset.setCustomerId(customer.getId()); + customerAsset = assetService.saveAsset(customerAsset); + + EntityRelation relation = new EntityRelation(); + relation.setFrom(customerAsset.getId()); + relation.setTo(tenantDevice.getId()); + relation.setAdditionalInfo(JacksonUtil.newObjectNode()); + relation.setType("Contains"); + relation.setTypeGroup(RelationTypeGroup.COMMON); + relationService.saveRelation(tenantId, relation); + + long ts = System.currentTimeMillis(); + Alarm tenantAlarm = Alarm.builder().tenantId(tenantId) + .originator(tenantDevice.getId()) + .type("Not Propagated") + .propagate(false) + .severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK) + .startTs(ts).build(); + AlarmOperationResult result = alarmService.createOrUpdateAlarm(tenantAlarm); + tenantAlarm = result.getAlarm(); + + Alarm customerAlarm = Alarm.builder().tenantId(tenantId) + .originator(tenantDevice.getId()) + .type("Propagated") + .propagate(true) + .severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK) + .startTs(ts).build(); + result = alarmService.createOrUpdateAlarm(customerAlarm); + customerAlarm = result.getAlarm(); + + AlarmDataPageLink pageLink = new AlarmDataPageLink(); + pageLink.setPage(0); + pageLink.setPageSize(10); + pageLink.setSortOrder(new EntityDataSortOrder(new EntityKey(EntityKeyType.ALARM_FIELD, "createdTime"))); + + pageLink.setStartTs(0L); + pageLink.setEndTs(System.currentTimeMillis()); + pageLink.setSearchPropagatedAlarms(true); + pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); + pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE)); + + //TEST that propagated alarms are visible on the asset level. + PageData customerAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, customer.getId(), toQuery(pageLink), Collections.singletonList(customerAsset.getId())); + Assert.assertEquals(1, customerAlarms.getData().size()); + Assert.assertEquals(customerAlarm, customerAlarms.getData().get(0)); + } + private AlarmDataQuery toQuery(AlarmDataPageLink pageLink){ return toQuery(pageLink, Collections.EMPTY_LIST); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java index 132e016f5f..87e307560c 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java @@ -15,6 +15,10 @@ */ package org.thingsboard.server.dao.service; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -34,6 +38,9 @@ import org.thingsboard.server.dao.exception.DataValidationException; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.stream.Collectors; public class BaseDeviceProfileServiceTest extends AbstractServiceTest { @@ -112,6 +119,23 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { Assert.assertNotNull(foundDefaultDeviceProfileInfo.getType()); } + @Test + public void testFindOrCreateDeviceProfile() throws ExecutionException, InterruptedException { + ListeningExecutorService testExecutor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(100)); + try { + List> futures = new ArrayList<>(); + for (int i = 0; i < 50; i ++) { + futures.add(testExecutor.submit(() -> deviceProfileService.findOrCreateDeviceProfile(tenantId, "Device Profile 1"))); + futures.add(testExecutor.submit(() -> deviceProfileService.findOrCreateDeviceProfile(tenantId, "Device Profile 2"))); + } + + List deviceProfiles = Futures.allAsList(futures).get(); + deviceProfiles.forEach(Assert::assertNotNull); + } finally { + testExecutor.shutdownNow(); + } + } + @Test public void testSetDefaultDeviceProfile() { DeviceProfile deviceProfile1 = this.createDeviceProfile(tenantId,"Device Profile 1"); diff --git a/docker/docker-compose.hybrid.yml b/docker/docker-compose.hybrid.yml index 20231292ce..6d8b0b7f9c 100644 --- a/docker/docker-compose.hybrid.yml +++ b/docker/docker-compose.hybrid.yml @@ -19,7 +19,7 @@ version: '2.2' services: postgres: restart: always - image: "postgres:11.6" + image: "postgres:12" ports: - "5432" environment: diff --git a/docker/docker-compose.kafka.yml b/docker/docker-compose.kafka.yml index c9b235008b..4df63eb8b8 100644 --- a/docker/docker-compose.kafka.yml +++ b/docker/docker-compose.kafka.yml @@ -19,7 +19,7 @@ version: '2.2' services: kafka: restart: always - image: "wurstmeister/kafka:2.12-2.3.0" + image: "wurstmeister/kafka:2.13-2.6.0" ports: - "9092:9092" env_file: diff --git a/docker/docker-compose.postgres.yml b/docker/docker-compose.postgres.yml index 28fe5a55e2..ee397dd814 100644 --- a/docker/docker-compose.postgres.yml +++ b/docker/docker-compose.postgres.yml @@ -19,7 +19,7 @@ version: '2.2' services: postgres: restart: always - image: "postgres:11.6" + image: "postgres:12" ports: - "5432" environment: diff --git a/msa/black-box-tests/pom.xml b/msa/black-box-tests/pom.xml index 74948b0e84..339b60741f 100644 --- a/msa/black-box-tests/pom.xml +++ b/msa/black-box-tests/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/js-executor/package.json b/msa/js-executor/package.json index 1f498922cd..8e7ba6d96e 100644 --- a/msa/js-executor/package.json +++ b/msa/js-executor/package.json @@ -1,7 +1,7 @@ { "name": "thingsboard-js-executor", "private": true, - "version": "3.2.0", + "version": "3.2.1", "description": "ThingsBoard JavaScript Executor Microservice", "main": "server.js", "bin": "server.js", @@ -19,7 +19,7 @@ "azure-sb": "^0.11.1", "config": "^3.3.1", "js-yaml": "^3.14.0", - "kafkajs": "^1.14.0", + "kafkajs": "^1.15.0", "long": "^4.0.0", "uuid-parse": "^1.1.0", "uuid-random": "^1.3.2", diff --git a/msa/js-executor/pom.xml b/msa/js-executor/pom.xml index ea286c58ba..692b371275 100644 --- a/msa/js-executor/pom.xml +++ b/msa/js-executor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/js-executor/yarn.lock b/msa/js-executor/yarn.lock index a2e65d950d..404c059c23 100644 --- a/msa/js-executor/yarn.lock +++ b/msa/js-executor/yarn.lock @@ -1665,10 +1665,10 @@ jws@^4.0.0: jwa "^2.0.0" safe-buffer "^5.0.1" -kafkajs@^1.14.0: - version "1.14.0" - resolved "https://registry.yarnpkg.com/kafkajs/-/kafkajs-1.14.0.tgz#3d998a77bfde54dc502e8e88690eedf0b21a1ed6" - integrity sha512-W+WCekiooY5rJP3Me5N3gWcQ8O6uG6lw0vv9t+sI+WqXKjKwj2+CWIXJy241x+ITE+1M1D19ABSiL2J8lKja5A== +kafkajs@^1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/kafkajs/-/kafkajs-1.15.0.tgz#a5ada0d933edca2149177393562be6fb0875ec3a" + integrity sha512-yjPyEnQCkPxAuQLIJnY5dI+xnmmgXmhuOQ1GVxClG5KTOV/rJcW1qA3UfvyEJKTp/RTSqQnUR3HJsKFvHyTpNg== keyv@^3.0.0: version "3.1.0" diff --git a/msa/pom.xml b/msa/pom.xml index 7419141995..cfdf6ed79f 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT thingsboard msa diff --git a/msa/tb-node/pom.xml b/msa/tb-node/pom.xml index 2d42d8f6dd..ad700779a9 100644 --- a/msa/tb-node/pom.xml +++ b/msa/tb-node/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/tb/docker-cassandra/start-db.sh b/msa/tb/docker-cassandra/start-db.sh index fa80f6db57..8491a979d2 100644 --- a/msa/tb/docker-cassandra/start-db.sh +++ b/msa/tb/docker-cassandra/start-db.sh @@ -27,7 +27,11 @@ fi exec setsid nohup postgres >> ${PGLOG}/postgres.log 2>&1 & if [ ! -f ${firstlaunch} ]; then - psql -U ${pkg.user} -d postgres -c "CREATE DATABASE thingsboard" + sleep 2 + while ! psql -U ${pkg.user} -d postgres -c "CREATE DATABASE thingsboard" + do + sleep 1 + done fi cassandra_data_dir=${CASSANDRA_DATA} diff --git a/msa/tb/docker-postgres/start-db.sh b/msa/tb/docker-postgres/start-db.sh index 6119daf2f9..618dae77da 100644 --- a/msa/tb/docker-postgres/start-db.sh +++ b/msa/tb/docker-postgres/start-db.sh @@ -27,5 +27,9 @@ fi exec setsid nohup postgres >> ${PGLOG}/postgres.log 2>&1 & if [ ! -f ${firstlaunch} ]; then - psql -U ${pkg.user} -d postgres -c "CREATE DATABASE thingsboard" -fi + sleep 2 + while ! psql -U ${pkg.user} -d postgres -c "CREATE DATABASE thingsboard" + do + sleep 1 + done +fi \ No newline at end of file diff --git a/msa/tb/pom.xml b/msa/tb/pom.xml index f41a3afd1e..d1901a3e33 100644 --- a/msa/tb/pom.xml +++ b/msa/tb/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/transport/coap/pom.xml b/msa/transport/coap/pom.xml index 0b3ab420e8..ad09511f36 100644 --- a/msa/transport/coap/pom.xml +++ b/msa/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/http/pom.xml b/msa/transport/http/pom.xml index 5621fd6bdc..97196bb879 100644 --- a/msa/transport/http/pom.xml +++ b/msa/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/mqtt/pom.xml b/msa/transport/mqtt/pom.xml index 24f8350706..79a26aeef3 100644 --- a/msa/transport/mqtt/pom.xml +++ b/msa/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/pom.xml b/msa/transport/pom.xml index e000241608..5a8b79d9d9 100644 --- a/msa/transport/pom.xml +++ b/msa/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/web-ui/package.json b/msa/web-ui/package.json index d839f97a33..792926a86d 100644 --- a/msa/web-ui/package.json +++ b/msa/web-ui/package.json @@ -1,7 +1,7 @@ { "name": "thingsboard-web-ui", "private": true, - "version": "3.2.0", + "version": "3.2.1", "description": "ThingsBoard Web UI Microservice", "main": "server.js", "bin": "server.js", diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml index f9ef249ce1..2902ea1c85 100644 --- a/msa/web-ui/pom.xml +++ b/msa/web-ui/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT msa org.thingsboard.msa diff --git a/netty-mqtt/pom.xml b/netty-mqtt/pom.xml index 58524a1a08..e917ce23f7 100644 --- a/netty-mqtt/pom.xml +++ b/netty-mqtt/pom.xml @@ -19,11 +19,11 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT thingsboard netty-mqtt - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT jar Netty MQTT Client diff --git a/pom.xml b/pom.xml index cc5bbce51b..257328b550 100755 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT pom Thingsboard @@ -36,12 +36,11 @@ ${project.name} /var/log/${pkg.name} /usr/share/${pkg.name} - 2.2.6.RELEASE - 2.1.2.RELEASE - 5.2.6.RELEASE - 5.2.3.RELEASE - 2.2.4.RELEASE - 3.1.0 + 2.3.5.RELEASE + 5.2.10.RELEASE + 5.4.1 + 2.4.1 + 3.3.0 0.7.0 2.2.0 4.12 @@ -52,15 +51,16 @@ 4.6.0 4.0.5 4.3.1.0 + 3.11.9 1.2.7 28.2-jre 2.6.1 3.4 2.5 1.4 - 2.10.2 - 2.10.2 - 2.10.2 + 2.11.3 + 2.11.3 + 2.11.3 2.2.6 1.0.2 2.6.2 @@ -72,7 +72,7 @@ 1.22.1 1.16.18 1.2.4 - 4.1.49.Final + 4.1.53.Final 1.5.0 4.8.0 2.19.1 @@ -92,12 +92,12 @@ 5.0.2 0.1.14 - 2.3.0 + 2.6.0 4.1.1 2.57 2.7.7 - 1.25 - 1.11.747 + 1.27 + 1.11.747 1.105.0 3.2.0 1.5.0 @@ -106,6 +106,9 @@ 3.2.2 1.5.0 1.5.2 + 1.0.2TB + 3.4.0 + 7.54.2 @@ -726,9 +729,11 @@ docker/haproxy/** docker/tb-node/** ui/** - src/browserslist + src/.browserslistrc + **/yarn.lock **/*.raw **/apache/cassandra/io/** + .run/** JAVADOC_STYLE @@ -872,11 +877,6 @@ spring-boot-starter-security ${spring-boot.version} - - org.springframework.cloud - spring-cloud-starter-oauth2 - ${spring-oauth2.version} - org.springframework.security spring-security-oauth2-client @@ -1200,6 +1200,11 @@ ${cassandra-unit.version} test + + org.apache.cassandra + cassandra-all + ${cassandra-all.version} + junit junit @@ -1316,7 +1321,12 @@ com.amazonaws aws-java-sdk-sqs - ${amazonaws.sqs.version} + ${aws.sdk.version} + + + com.amazonaws + aws-java-sdk-sns + ${aws.sdk.version} com.google.cloud @@ -1369,6 +1379,35 @@ micrometer-registry-prometheus ${micrometer.version} + + org.thingsboard + protobuf-dynamic + ${protobuf-dynamic.version} + + + com.squareup.wire + wire-schema + ${wire-schema.version} + + + com.twilio.sdk + twilio + ${twilio.version} + + + io.jsonwebtoken + jjwt-api + + + io.jsonwebtoken + jjwt-jackson + + + io.jsonwebtoken + jjwt-impl + + + diff --git a/rest-client/pom.xml b/rest-client/pom.xml index a7b97610d1..89f6b33a02 100644 --- a/rest-client/pom.xml +++ b/rest-client/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT thingsboard rest-client diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index 0a92d4b1a8..b79987df62 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -109,6 +109,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.security.model.SecuritySettings; import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; +import org.thingsboard.server.common.data.sms.config.TestSmsRequest; import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetsBundle; @@ -218,7 +219,11 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } public void sendTestMail(AdminSettings adminSettings) { - restTemplate.postForEntity(baseURL + "/api/admin/settings/testMail", adminSettings, AdminSettings.class); + restTemplate.postForLocation(baseURL + "/api/admin/settings/testMail", adminSettings); + } + + public void sendTestSms(TestSmsRequest testSmsRequest) { + restTemplate.postForLocation(baseURL + "/api/admin/settings/testSms", testSmsRequest); } public Optional getSecuritySettings() { @@ -1451,7 +1456,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { params.put("relationTypeGroup", relationTypeGroup.name()); return restTemplate.exchange( - baseURL + "/api/relations?toId={toId}&toType={toType}&relationTypeGroup={relationTypeGroup}", + baseURL + "/api/relations/info?toId={toId}&toType={toType}&relationTypeGroup={relationTypeGroup}", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1486,7 +1491,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { public List findInfoByQuery(EntityRelationsQuery query) { return restTemplate.exchange( - baseURL + "/api/relations", + baseURL + "/api/relations/info", HttpMethod.POST, new HttpEntity<>(query), new ParameterizedTypeReference>() { @@ -1714,6 +1719,10 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return restTemplate.postForEntity(baseURL + "/api/oauth2/config", oauth2Params, OAuth2ClientsParams.class).getBody(); } + public String getLoginProcessingUrl() { + return restTemplate.getForEntity(baseURL + "/api/oauth2/loginProcessingUrl", String.class).getBody(); + } + public void handleOneWayDeviceRPCRequest(DeviceId deviceId, JsonNode requestBody) { restTemplate.postForLocation(baseURL + "/api/plugins/rpc/oneway/{deviceId}", requestBody, deviceId.getId()); } diff --git a/rule-engine/pom.xml b/rule-engine/pom.xml index 197c4e500d..43bd3859af 100644 --- a/rule-engine/pom.xml +++ b/rule-engine/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT thingsboard rule-engine diff --git a/rule-engine/rule-engine-api/pom.xml b/rule-engine/rule-engine-api/pom.xml index 1a4c0b8506..448b5f8760 100644 --- a/rule-engine/rule-engine-api/pom.xml +++ b/rule-engine/rule-engine-api/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT rule-engine org.thingsboard.rule-engine diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java index a36089107d..2add686349 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java @@ -16,7 +16,11 @@ package org.thingsboard.rule.engine.api; import com.fasterxml.jackson.databind.JsonNode; +import org.thingsboard.server.common.data.ApiFeature; +import org.thingsboard.server.common.data.ApiUsageStateMailMessage; +import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.TenantId; import javax.mail.MessagingException; @@ -24,7 +28,7 @@ public interface MailService { void updateMailConfiguration(); - void sendEmail(String email, String subject, String message) throws ThingsboardException; + void sendEmail(TenantId tenantId, String email, String subject, String message) throws ThingsboardException; void sendTestMail(JsonNode config, String email) throws ThingsboardException; @@ -36,7 +40,9 @@ public interface MailService { void sendPasswordWasResetEmail(String loginLink, String email) throws ThingsboardException; - void send(String from, String to, String cc, String bcc, String subject, String body) throws MessagingException; + void send(TenantId tenantId, String from, String to, String cc, String bcc, String subject, String body) throws MessagingException; void sendAccountLockoutEmail( String lockoutEmail, String email, Integer maxFailedLoginAttempts) throws ThingsboardException; + + void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage msg) throws ThingsboardException; } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/SmsService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/SmsService.java new file mode 100644 index 0000000000..01b82931a6 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/SmsService.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2020 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.thingsboard.server.common.data.sms.config.TestSmsRequest; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.TenantId; + +public interface SmsService { + + void updateSmsConfiguration(); + + void sendSms(TenantId tenantId, String[] numbersTo, String message) throws ThingsboardException;; + + void sendTestSms(TestSmsRequest testSmsRequest) throws ThingsboardException; + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index 081a84eef0..8dd9add1bd 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -18,9 +18,12 @@ package org.thingsboard.rule.engine.api; import io.netty.channel.EventLoopGroup; import org.springframework.data.redis.core.RedisTemplate; import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.DeviceId; @@ -194,12 +197,18 @@ public interface TbContext { ListeningExecutor getMailExecutor(); + ListeningExecutor getSmsExecutor(); + ListeningExecutor getDbCallbackExecutor(); ListeningExecutor getExternalCallExecutor(); MailService getMailService(); + SmsService getSmsService(); + + SmsSenderFactory getSmsSenderFactory(); + ScriptEngine createJsScriptEngine(String script, String... argNames); void logJsEvalRequest(); @@ -229,7 +238,11 @@ public interface TbContext { void clearRuleNodeStates(); + void addTenantProfileListener(Consumer listener); + void addDeviceProfileListeners(Consumer listener, BiConsumer deviceListener); - void removeProfileListener(); + void removeListeners(); + + TenantProfile getTenantProfile(); } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSender.java similarity index 70% rename from common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java rename to rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSender.java index d124fd48b0..672992df64 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSender.java @@ -13,17 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.msg.session; +package org.thingsboard.rule.engine.api.sms; -import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.rule.engine.api.sms.exception.SmsException; -import java.util.UUID; +public interface SmsSender { -public interface SessionContext { + int sendSms(String numberTo, String message) throws SmsException; - UUID getSessionId(); + void destroy(); - int nextMsgId(); - - void onProfileUpdate(DeviceProfile deviceProfile); } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSenderFactory.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSenderFactory.java new file mode 100644 index 0000000000..5930601c22 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/SmsSenderFactory.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2020 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.sms; + +import org.thingsboard.server.common.data.sms.config.SmsProviderConfiguration; + +public interface SmsSenderFactory { + + SmsSender createSmsSender(SmsProviderConfiguration config); + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsException.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsException.java new file mode 100644 index 0000000000..dce599404c --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsException.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2020 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.sms.exception; + +public abstract class SmsException extends RuntimeException { + + public SmsException(String msg) { + super(msg); + } + + public SmsException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsParseException.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsParseException.java new file mode 100644 index 0000000000..de3c41029e --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsParseException.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2020 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.sms.exception; + +public class SmsParseException extends SmsException { + + public SmsParseException(String msg) { + super(msg); + } + + public SmsParseException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsSendException.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsSendException.java new file mode 100644 index 0000000000..f5254bf6d6 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/sms/exception/SmsSendException.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2020 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.sms.exception; + +public class SmsSendException extends SmsException { + + public SmsSendException(String msg) { + super(msg); + } + + public SmsSendException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index c4d2b66d15..9fb02ec1f0 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT rule-engine org.thingsboard.rule-engine @@ -120,6 +120,11 @@ org.locationtech.jts jts-core + + com.sun.mail + javax.mail + provided + junit junit 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 3ca2d5dbab..7c1bc67e21 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 @@ -26,6 +26,7 @@ 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.util.TbNodeUtils; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -87,7 +88,7 @@ public class TbSendEmailNode implements TbNode { private void sendEmail(TbContext ctx, EmailPojo email) throws Exception { if (this.config.isUseSystemSmtpSettings()) { - ctx.getMailService().send(email.getFrom(), email.getTo(), email.getCc(), + ctx.getMailService().send(ctx.getTenantId(), email.getFrom(), email.getTo(), email.getCc(), email.getBcc(), email.getSubject(), email.getBody()); } else { MimeMessage mailMsg = mailSender.createMimeMessage(); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index ce43ca260f..0a501a0890 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.Data; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.thingsboard.rule.engine.action.TbAlarmResult; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.profile.state.PersistedAlarmRuleState; @@ -111,7 +112,7 @@ class AlarmState { for (AlarmRuleState state : createRulesSortedBySeverityDesc) { stateUpdate = clearAlarmState(stateUpdate, state); } - ctx.getAlarmService().clearAlarm(ctx.getTenantId(), currentAlarm.getId(), JacksonUtil.OBJECT_MAPPER.createObjectNode(), System.currentTimeMillis()); + ctx.getAlarmService().clearAlarm(ctx.getTenantId(), currentAlarm.getId(), createDetails(clearState), System.currentTimeMillis()); pushMsg(ctx, new TbAlarmResult(false, false, true, currentAlarm)); currentAlarm = null; } else if (AlarmEvalResult.FALSE.equals(evalResult)) { @@ -122,8 +123,10 @@ class AlarmState { } public boolean clearAlarmState(boolean stateUpdate, AlarmRuleState state) { - state.clear(); - stateUpdate |= state.checkUpdate(); + if (state != null) { + state.clear(); + stateUpdate |= state.checkUpdate(); + } return stateUpdate; } @@ -203,6 +206,7 @@ class AlarmState { AlarmSeverity oldSeverity = currentAlarm.getSeverity(); // Skip update if severity is decreased. if (severity.ordinal() <= oldSeverity.ordinal()) { + currentAlarm.setDetails(createDetails(ruleState)); if (!oldSeverity.equals(severity)) { currentAlarm.setSeverity(severity); currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm); @@ -235,19 +239,24 @@ class AlarmState { } private JsonNode createDetails(AlarmRuleState ruleState) { - ObjectNode details = JacksonUtil.OBJECT_MAPPER.createObjectNode(); - String alarmDetails = ruleState.getAlarmRule().getAlarmDetails(); + JsonNode alarmDetails; + String alarmDetailsStr = ruleState.getAlarmRule().getAlarmDetails(); - if (alarmDetails != null) { + if (StringUtils.isNotEmpty(alarmDetailsStr)) { for (KeyFilter keyFilter : ruleState.getAlarmRule().getCondition().getCondition()) { EntityKeyValue entityKeyValue = dataSnapshot.getValue(keyFilter.getKey()); - alarmDetails = alarmDetails.replaceAll(String.format("\\$\\{%s}", keyFilter.getKey().getKey()), getValueAsString(entityKeyValue)); + alarmDetailsStr = alarmDetailsStr.replaceAll(String.format("\\$\\{%s}", keyFilter.getKey().getKey()), getValueAsString(entityKeyValue)); } - - details.put("data", alarmDetails); + ObjectNode newDetails = JacksonUtil.newObjectNode(); + newDetails.put("data", alarmDetailsStr); + alarmDetails = newDetails; + } else if (currentAlarm != null) { + alarmDetails = currentAlarm.getDetails(); + } else { + alarmDetails = JacksonUtil.newObjectNode(); } - return details; + return alarmDetails; } private static String getValueAsString(EntityKeyValue entityKeyValue) { 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 fa7538bce6..27852316af 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 @@ -54,7 +54,8 @@ import java.util.concurrent.TimeUnit; relationTypes = {"Alarm Created", "Alarm Updated", "Alarm Severity Updated", "Alarm Cleared", "Success", "Failure"}, configClazz = TbDeviceProfileNodeConfiguration.class, nodeDescription = "Process device messages based on device profile settings", - nodeDetails = "Create and clear alarms based on alarm rules defined in device profile. Generates ", + 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" ) @@ -119,7 +120,6 @@ public class TbDeviceProfileNode implements TbNode { } else { removeDeviceState(deviceId); } - } else { if (EntityType.DEVICE.equals(originatorType)) { DeviceId deviceId = new DeviceId(msg.getOriginator().getId()); @@ -149,7 +149,7 @@ public class TbDeviceProfileNode implements TbNode { @Override public void destroy() { - ctx.removeProfileListener(); + ctx.removeListeners(); deviceStates.clear(); } 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 new file mode 100644 index 0000000000..d06ecdd3fe --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNode.java @@ -0,0 +1,98 @@ +/** + * Copyright © 2016-2020 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.sms; + +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.rule.engine.api.RuleNode; +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.sms.SmsSender; +import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.server.common.data.ApiUsageRecordKey; +import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.msg.TbMsg; + +import static org.thingsboard.common.util.DonAsynchron.withCallback; + +@Slf4j +@RuleNode( + type = ComponentType.EXTERNAL, + name = "send sms", + 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 = "tbActionNodeSendSmsConfig", + icon = "sms" +) +public class TbSendSmsNode implements TbNode { + + private TbSendSmsNodeConfiguration config; + private SmsSender smsSender; + + @Override + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { + try { + this.config = TbNodeUtils.convert(configuration, TbSendSmsNodeConfiguration.class); + if (!this.config.isUseSystemSmsSettings()) { + smsSender = createSmsSender(ctx); + } + } catch (Exception e) { + throw new TbNodeException(e); + } + } + + @Override + public void onMsg(TbContext ctx, TbMsg msg) { + try { + withCallback(ctx.getSmsExecutor().executeAsync(() -> { + sendSms(ctx, msg); + return null; + }), + ok -> ctx.tellSuccess(msg), + fail -> ctx.tellFailure(msg, fail)); + } catch (Exception ex) { + ctx.tellFailure(msg, ex); + } + } + + private void sendSms(TbContext ctx, TbMsg msg) throws Exception { + String numbersTo = TbNodeUtils.processPattern(this.config.getNumbersToTemplate(), msg.getMetaData()); + String message = TbNodeUtils.processPattern(this.config.getSmsMessageTemplate(), msg.getMetaData()); + String[] numbersToList = numbersTo.split(","); + if (this.config.isUseSystemSmsSettings()) { + ctx.getSmsService().sendSms(ctx.getTenantId(), numbersToList, message); + } else { + for (String numberTo : numbersToList) { + this.smsSender.sendSms(numberTo, message); + } + } + } + + @Override + public void destroy() { + if (this.smsSender != null) { + this.smsSender.destroy(); + } + } + + private SmsSender createSmsSender(TbContext ctx) { + return ctx.getSmsSenderFactory().createSmsSender(this.config.getSmsProviderConfiguration()); + } + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNodeConfiguration.java new file mode 100644 index 0000000000..7bbe1d9217 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNodeConfiguration.java @@ -0,0 +1,38 @@ +/** + * Copyright © 2016-2020 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.sms; + +import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.server.common.data.sms.config.SmsProviderConfiguration; + +@Data +public class TbSendSmsNodeConfiguration implements NodeConfiguration { + + private String numbersToTemplate; + private String smsMessageTemplate; + private boolean useSystemSmsSettings; + private SmsProviderConfiguration smsProviderConfiguration; + + @Override + public NodeConfiguration defaultConfiguration() { + TbSendSmsNodeConfiguration configuration = new TbSendSmsNodeConfiguration(); + configuration.numbersToTemplate = "${userPhone}"; + configuration.smsMessageTemplate = "Device ${deviceName} has high temperature ${temp}"; + configuration.setUseSystemSmsSettings(true); + return configuration; + } +} 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 a415e08695..e803392d5b 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 @@ -24,6 +24,7 @@ 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.server.common.data.TenantProfile; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; @@ -31,10 +32,12 @@ import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; @Slf4j @RuleNode( @@ -50,10 +53,20 @@ import java.util.Map; public class TbMsgTimeseriesNode implements TbNode { private TbMsgTimeseriesNodeConfiguration config; + private TbContext ctx; + private long tenantProfileDefaultStorageTtl; @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()); + } + + void onTenantProfileUpdate(TenantProfile tenantProfile) { + DefaultTenantProfileConfiguration configuration = (DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration(); + tenantProfileDefaultStorageTtl = TimeUnit.DAYS.toSeconds(configuration.getDefaultStorageTtlDays()); } @Override @@ -77,6 +90,9 @@ public class TbMsgTimeseriesNode implements TbNode { } String ttlValue = msg.getMetaData().getValue("TTL"); long ttl = !StringUtils.isEmpty(ttlValue) ? Long.parseLong(ttlValue) : config.getDefaultTTL(); + if (ttl == 0L) { + ttl = tenantProfileDefaultStorageTtl; + } ctx.getTelemetryService().saveAndNotify(ctx.getTenantId(), msg.getOriginator(), tsKvEntryList, ttl, new TelemetryNodeCallback(ctx, msg)); } @@ -96,6 +112,7 @@ public class TbMsgTimeseriesNode implements TbNode { @Override public void destroy() { + ctx.removeListeners(); } } 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 index a8bd79d68b..0926cb41cb 100644 --- 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 @@ -1,4 +1,4 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@angular/core"),require("@angular/common"),require("@ngx-translate/core"),require("@shared/public-api"),require("@ngrx/store"),require("@angular/forms"),require("@core/public-api"),require("@angular/cdk/keycodes"),require("@home/components/public-api"),require("@angular/cdk/coercion"),require("@angular/material/chips"),require("@angular/material/autocomplete"),require("rxjs"),require("rxjs/operators")):"function"==typeof define&&define.amd?define("rulenode-core-config",["exports","@angular/core","@angular/common","@ngx-translate/core","@shared/public-api","@ngrx/store","@angular/forms","@core/public-api","@angular/cdk/keycodes","@home/components/public-api","@angular/cdk/coercion","@angular/material/chips","@angular/material/autocomplete","rxjs","rxjs/operators"],t):t((e=e||self)["rulenode-core-config"]={},e.ng.core,e.ng.common,e["ngx-translate"],e.shared,e["ngrx-store"],e.ng.forms,e.core,e.ng.cdk.keycodes,e.publicApi$2,e.ng.cdk.coercion,e.ng.material.chips,e.ng.material.autocomplete,e.rxjs,e.rxjs.operators)}(this,(function(e,t,r,n,a,o,i,l,s,m,u,d,p,c,f){"use strict"; +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@angular/core"),require("@angular/common"),require("@ngx-translate/core"),require("@shared/public-api"),require("@ngrx/store"),require("@angular/forms"),require("@home/components/public-api"),require("@core/public-api"),require("@angular/cdk/keycodes"),require("@angular/cdk/coercion"),require("@angular/material/chips"),require("@angular/material/autocomplete"),require("rxjs"),require("rxjs/operators")):"function"==typeof define&&define.amd?define("rulenode-core-config",["exports","@angular/core","@angular/common","@ngx-translate/core","@shared/public-api","@ngrx/store","@angular/forms","@home/components/public-api","@core/public-api","@angular/cdk/keycodes","@angular/cdk/coercion","@angular/material/chips","@angular/material/autocomplete","rxjs","rxjs/operators"],t):t((e=e||self)["rulenode-core-config"]={},e.ng.core,e.ng.common,e["ngx-translate"],e.shared,e["ngrx-store"],e.ng.forms,e.publicApi$1,e.core,e.ng.cdk.keycodes,e.ng.cdk.coercion,e.ng.material.chips,e.ng.material.autocomplete,e.rxjs,e.rxjs.operators)}(this,(function(e,t,r,n,a,o,i,l,s,m,u,d,p,c,f){"use strict"; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. @@ -12,5 +12,5 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - ***************************************************************************** */var g=function(e,t){return(g=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])})(e,t)};function y(e,t){function r(){this.constructor=e}g(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}function b(e,t,r,n){var a,o=arguments.length,i=o<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,r):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,r,n);else for(var l=e.length-1;l>=0;l--)(a=e[l])&&(i=(o<3?a(i):o>3?a(t,r,i):a(t,r))||i);return o>3&&i&&Object.defineProperty(t,r,i),i}function h(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}Object.create;function C(e){var t="function"==typeof Symbol&&Symbol.iterator,r=t&&e[t],n=0;if(r)return r.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&n>=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}Object.create;var v,F=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.emptyConfigForm},r.prototype.onConfigurationSet=function(e){this.emptyConfigForm=this.fb.group({})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-node-empty-config",template:"
"}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),x=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.attributeScopes=Object.keys(a.AttributeScope),n.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.attributesConfigForm},r.prototype.onConfigurationSet=function(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[i.Validators.required]],notifyDevice:[!e||e.scope,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-attributes-config",template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
tb.rulenode.notify-device-hint
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),T=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.timeseriesConfigForm},r.prototype.onConfigurationSet=function(e){this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-timeseries-config",template:'
\n \n tb.rulenode.default-ttl\n \n \n {{ \'tb.rulenode.default-ttl-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-default-ttl-message\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),q=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcRequestConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rpc-request-config",template:'
\n \n tb.rulenode.timeout-sec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-message\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),S=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.logConfigForm},r.prototype.onConfigurationSet=function(e){this.logConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.logConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.logConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),I=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.assignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.create-customer-if-not-exists\' | translate }}\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),k=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.clearAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.clearAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],alarmType:[e?e.alarmType:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.clearAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-clear-alarm-config",template:'
\n \n \n \n
\n \n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),N=function(e){function r(t,r,n,o){var i=e.call(this,t)||this;return i.store=t,i.fb=r,i.nodeScriptTestService=n,i.translate=o,i.alarmSeverities=Object.keys(a.AlarmSeverity),i.alarmSeverityTranslationMap=a.alarmSeverityTranslations,i.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],i}return y(r,e),r.prototype.configForm=function(){return this.createAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.createAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]]})},r.prototype.validatorTriggers=function(){return["useMessageAlarmData"]},r.prototype.updateValidators=function(e){this.createAlarmConfigForm.get("useMessageAlarmData").value?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([i.Validators.required]),this.createAlarmConfigForm.get("severity").setValidators([i.Validators.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e})},r.prototype.testScript=function(){var e=this,t=this.createAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.createAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.removeKey=function(e,t){var r=this.createAlarmConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.createAlarmConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.createAlarmConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.createAlarmConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-create-alarm-config",template:'
\n \n \n \n
\n \n
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n
\n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n \n tb.rulenode.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n
\n \n {{ \'tb.rulenode.propagate\' | translate }}\n \n
\n \n tb.rulenode.relation-types-list\n \n \n {{key}}\n close\n \n \n \n \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),V=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.createRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[i.Validators.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["entityType"]},r.prototype.updateValidators=function(e){var t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==a.EntityType.DEVICE&&t!==a.EntityType.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([i.Validators.required]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-create-relation-config",template:'
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.create-entity-if-not-exists\' | translate }}\n \n
tb.rulenode.create-entity-if-not-exists-hint
\n
\n \n {{ \'tb.rulenode.remove-current-relations\' | translate }}\n \n
tb.rulenode.remove-current-relations-hint
\n \n {{ \'tb.rulenode.change-originator-to-related-entity\' | translate }}\n \n
tb.rulenode.change-originator-to-related-entity-hint
\n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),E=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgDelayConfigForm},r.prototype.onConfigurationSet=function(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,[i.Validators.required,i.Validators.min(1),i.Validators.max(1e5)]]})},r.prototype.validatorTriggers=function(){return["useMetadataPeriodInSecondsPatterns"]},r.prototype.updateValidators=function(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([i.Validators.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([i.Validators.required,i.Validators.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-msg-delay-config",template:'
\n \n {{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
\n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-0-seconds-message\' | translate }}\n \n \n \n \n tb.rulenode.period-in-seconds-pattern\n \n \n {{ \'tb.rulenode.period-in-seconds-pattern-required\' | translate }}\n \n \n \n \n \n tb.rulenode.max-pending-messages\n \n \n {{ \'tb.rulenode.max-pending-messages-required\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),A=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.deleteRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["deleteForSingleEntity","entityType"]},r.prototype.updateValidators=function(e){var t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,r=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&r?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-delete-relation-config",template:'
\n \n {{ \'tb.rulenode.delete-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.delete-relation-hint
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),L=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.generatorConfigForm},r.prototype.onConfigurationSet=function(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[i.Validators.required,i.Validators.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[i.Validators.required,i.Validators.min(1)]],originator:[e?e.originator:null,[]],jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.prepareInputConfig=function(e){return e&&(e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e},r.prototype.prepareOutputConfig=function(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e},r.prototype.testScript=function(){var e=this,t=this.generatorConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId).subscribe((function(t){t&&e.generatorConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-generator-config",template:'
\n \n tb.rulenode.message-count\n \n \n {{ \'tb.rulenode.message-count-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-message-count-message\' | translate }}\n \n \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-seconds-message\' | translate }}\n \n \n
\n \n \n \n
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent);!function(e){e.CUSTOMER="CUSTOMER",e.TENANT="TENANT",e.RELATED="RELATED",e.ALARM_ORIGINATOR="ALARM_ORIGINATOR"}(v||(v={}));var M,P=new Map([[v.CUSTOMER,"tb.rulenode.originator-customer"],[v.TENANT,"tb.rulenode.originator-tenant"],[v.RELATED,"tb.rulenode.originator-related"],[v.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]);!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(M||(M={}));var R,w=new Map([[M.CIRCLE,"tb.rulenode.perimeter-circle"],[M.POLYGON,"tb.rulenode.perimeter-polygon"]]);!function(e){e.MILLISECONDS="MILLISECONDS",e.SECONDS="SECONDS",e.MINUTES="MINUTES",e.HOURS="HOURS",e.DAYS="DAYS"}(R||(R={}));var O,D=new Map([[R.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[R.SECONDS,"tb.rulenode.time-unit-seconds"],[R.MINUTES,"tb.rulenode.time-unit-minutes"],[R.HOURS,"tb.rulenode.time-unit-hours"],[R.DAYS,"tb.rulenode.time-unit-days"]]);!function(e){e.METER="METER",e.KILOMETER="KILOMETER",e.FOOT="FOOT",e.MILE="MILE",e.NAUTICAL_MILE="NAUTICAL_MILE"}(O||(O={}));var K,B=new Map([[O.METER,"tb.rulenode.range-unit-meter"],[O.KILOMETER,"tb.rulenode.range-unit-kilometer"],[O.FOOT,"tb.rulenode.range-unit-foot"],[O.MILE,"tb.rulenode.range-unit-mile"],[O.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);!function(e){e.TITLE="TITLE",e.COUNTRY="COUNTRY",e.STATE="STATE",e.ZIP="ZIP",e.ADDRESS="ADDRESS",e.ADDRESS2="ADDRESS2",e.PHONE="PHONE",e.EMAIL="EMAIL",e.ADDITIONAL_INFO="ADDITIONAL_INFO"}(K||(K={}));var U,j,H,G=new Map([[K.TITLE,"tb.rulenode.entity-details-title"],[K.COUNTRY,"tb.rulenode.entity-details-country"],[K.STATE,"tb.rulenode.entity-details-state"],[K.ZIP,"tb.rulenode.entity-details-zip"],[K.ADDRESS,"tb.rulenode.entity-details-address"],[K.ADDRESS2,"tb.rulenode.entity-details-address2"],[K.PHONE,"tb.rulenode.entity-details-phone"],[K.EMAIL,"tb.rulenode.entity-details-email"],[K.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(U||(U={})),function(e){e.ASC="ASC",e.DESC="DESC"}(j||(j={})),function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(H||(H={}));var z,$=new Map([[H.STANDARD,"tb.rulenode.sqs-queue-standard"],[H.FIFO,"tb.rulenode.sqs-queue-fifo"]]),Q=["anonymous","basic","cert.PEM"],_=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),W=["sas","cert.PEM"],J=new Map([["sas","tb.rulenode.credentials-sas"],["cert.PEM","tb.rulenode.credentials-pem"]]);!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE"}(z||(z={}));var Y=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],Z=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"]]),X=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=B,n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=D,n}return y(r,e),r.prototype.configForm=function(){return this.geoActionConfigForm},r.prototype.onConfigurationSet=function(e){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType: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,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[i.Validators.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterType").setValidators([]):this.geoActionConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoActionConfigForm.get("centerLatitude").setValidators([]),this.geoActionConfigForm.get("centerLongitude").setValidators([]),this.geoActionConfigForm.get("range").setValidators([]),this.geoActionConfigForm.get("rangeUnit").setValidators([])):(this.geoActionConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoActionConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoActionConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),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})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.min-inside-duration\n \n \n {{ \'tb.rulenode.min-inside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-inside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.min-outside-duration\n \n \n {{ \'tb.rulenode.min-outside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-outside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgCountConfigForm},r.prototype.onConfigurationSet=function(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[i.Validators.required,i.Validators.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-msg-count-config",template:'
\n \n tb.rulenode.interval-seconds\n \n \n {{ \'tb.rulenode.interval-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-interval-seconds-message\' | translate }}\n \n \n \n tb.rulenode.output-timeseries-key-prefix\n \n \n {{ \'tb.rulenode.output-timeseries-key-prefix-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcReplyConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rpc-reply-config",template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.saveToCustomTableConfigForm},r.prototype.onConfigurationSet=function(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[i.Validators.required]],fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-custom-table-config",template:'
\n \n tb.rulenode.custom-table-name\n \n \n {{ \'tb.rulenode.custom-table-name-required\' | translate }}\n \n \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ne=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.translate=r,o.injector=n,o.fb=a,o.propagateChange=null,o.valueChangeSubscription=null,o}var a;return y(r,e),a=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){this.ngControl=this.injector.get(i.NgControl),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))},r.prototype.keyValsFormArray=function(){return this.kvListFormGroup.get("keyVals")},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t,r,n=this;this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();var a=[];if(e)try{for(var o=C(Object.keys(e)),l=o.next();!l.done;l=o.next()){var s=l.value;Object.prototype.hasOwnProperty.call(e,s)&&a.push(this.fb.group({key:[s,[i.Validators.required]],value:[e[s],[i.Validators.required]]}))}}catch(e){t={error:e}}finally{try{l&&!l.done&&(r=o.return)&&r.call(o)}finally{if(t)throw t.error}}this.kvListFormGroup.setControl("keyVals",this.fb.array(a)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((function(){n.updateModel()}))},r.prototype.removeKeyVal=function(e){this.kvListFormGroup.get("keyVals").removeAt(e)},r.prototype.addKeyVal=function(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[i.Validators.required]],value:["",[i.Validators.required]]}))},r.prototype.validate=function(e){return!this.kvListFormGroup.get("keyVals").value.length&&this.required?{kvMapRequired:!0}:this.kvListFormGroup.valid?null:{kvFieldsRequired:!0}},r.prototype.updateModel=function(){var e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{var t={};e.forEach((function(e){t[e.key]=e.value})),this.propagateChange(t)}},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:t.Injector},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",String)],r.prototype,"requiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyRequiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valRequiredText",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=a=b([t.Component({selector:"tb-kv-map-config",template:'
\n
\n {{ keyText }}\n {{ valText }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n \n
\n \n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return a})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return a})),multi:!0}],styles:[":host .tb-kv-map-config{margin-bottom:16px}:host .tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}:host .tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .row{padding-top:5px;max-height:40px}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0;max-height:40px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0}"]}),h("design:paramtypes",[o.Store,n.TranslateService,t.Injector,i.FormBuilder])],r)}(a.PageComponent),ae=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.deviceRelationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[i.Validators.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((function(t){e.deviceRelationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-device-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((function(t){e.relationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.relationsQueryFormGroup.reset(e||{},{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-filters
\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),ie=function(e){function r(t,r,n,o){var i,l,m=e.call(this,t)||this;m.store=t,m.translate=r,m.truncate=n,m.fb=o,m.placeholder="tb.rulenode.message-type",m.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],m.messageTypes=[],m.messageTypesList=[],m.searchText="",m.propagateChange=function(e){},m.messageTypeConfigForm=m.fb.group({messageType:[null]});try{for(var u=C(Object.keys(a.MessageType)),d=u.next();!d.done;d=u.next()){var p=d.value;m.messageTypesList.push({name:a.messageTypeNames.get(a.MessageType[p]),value:p})}}catch(e){i={error:e}}finally{try{d&&!d.done&&(l=u.return)&&l.call(u)}finally{if(i)throw i.error}}return m}var l;return y(r,e),l=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.ngOnInit=function(){var e=this;this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(t){return e.fetchMessageTypes(t)})),f.share())},r.prototype.ngAfterViewInit=function(){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t=this;this.searchText="",this.messageTypes.length=0,e&&e.forEach((function(e){var r=t.messageTypesList.find((function(t){return t.value===e}));r?t.messageTypes.push({name:r.name,value:r.value}):t.messageTypes.push({name:e,value:e})}))},r.prototype.displayMessageTypeFn=function(e){return e?e.name:void 0},r.prototype.textIsNotEmpty=function(e){return!!(e&&null!=e&&e.length>0)},r.prototype.createMessageType=function(e,t){e.preventDefault(),this.transformMessageType(t)},r.prototype.add=function(e){this.transformMessageType(e.value)},r.prototype.fetchMessageTypes=function(e){if(this.searchText=e,this.searchText&&this.searchText.length){var t=this.searchText.toUpperCase();return c.of(this.messageTypesList.filter((function(e){return e.name.toUpperCase().includes(t)})))}return c.of(this.messageTypesList)},r.prototype.transformMessageType=function(e){if((e||"").trim()){var t=null,r=e.trim(),n=this.messageTypesList.find((function(e){return e.name===r}));(t=n?{name:n.name,value:n.value}:{name:r,value:r})&&this.addMessageType(t)}this.clear("")},r.prototype.remove=function(e){var t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())},r.prototype.selected=function(e){this.addMessageType(e.option.value),this.clear("")},r.prototype.addMessageType=function(e){-1===this.messageTypes.findIndex((function(t){return t.value===e.value}))&&(this.messageTypes.push(e),this.updateModel())},r.prototype.onFocus=function(){this.messageTypeConfigForm.get("messageType").updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((function(){t.messageTypeInput.nativeElement.blur(),t.messageTypeInput.nativeElement.focus()}),0)},r.prototype.updateModel=function(){var e=this.messageTypes.map((function(e){return e.value}));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:a.TruncatePipe},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),b([t.Input(),h("design:type",String)],r.prototype,"label",void 0),b([t.Input(),h("design:type",Object)],r.prototype,"placeholder",void 0),b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.ViewChild("chipList",{static:!1}),h("design:type",d.MatChipList)],r.prototype,"chipList",void 0),b([t.ViewChild("messageTypeAutocomplete",{static:!1}),h("design:type",p.MatAutocomplete)],r.prototype,"matAutocomplete",void 0),b([t.ViewChild("messageTypeInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"messageTypeInput",void 0),r=l=b([t.Component({selector:"tb-message-types-config",template:'\n {{ label }}\n \n \n {{messageType.name}}\n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-message-types-found\n
\n \n \n {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\n \n \n \n tb.rulenode.create-new-message-type\n \n
\n
\n
\n \n {{ \'tb.rulenode.message-types-required\' | translate }}\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return l})),multi:!0}]}),h("design:paramtypes",[o.Store,n.TranslateService,a.TruncatePipe,i.FormBuilder])],r)}(a.PageComponent),le=function(){function e(){}return e=b([t.NgModule({declarations:[ne,ae,oe,ie],imports:[r.CommonModule,a.SharedModule,m.HomeComponentsModule],exports:[ne,ae,oe,ie]})],e)}(),se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.unassignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-un-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.snsConfigForm},r.prototype.onConfigurationSet=function(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[i.Validators.required]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-sns-config",template:'
\n \n tb.rulenode.topic-arn-pattern\n \n \n {{ \'tb.rulenode.topic-arn-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ue=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.sqsQueueType=H,n.sqsQueueTypes=Object.keys(H),n.sqsQueueTypeTranslationsMap=$,n}return y(r,e),r.prototype.configForm=function(){return this.sqsConfigForm},r.prototype.onConfigurationSet=function(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[i.Validators.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[i.Validators.required]],delaySeconds:[e?e.delaySeconds:null,[i.Validators.min(0),i.Validators.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-sqs-config",template:'
\n \n tb.rulenode.queue-type\n \n \n {{ sqsQueueTypeTranslationsMap.get(type) | translate }}\n \n \n \n \n tb.rulenode.queue-url-pattern\n \n \n {{ \'tb.rulenode.queue-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.delay-seconds\n \n \n {{ \'tb.rulenode.min-delay-seconds-message\' | translate }}\n \n \n {{ \'tb.rulenode.max-delay-seconds-message\' | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),de=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.pubSubConfigForm},r.prototype.onConfigurationSet=function(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[i.Validators.required]],topicName:[e?e.topicName:null,[i.Validators.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[i.Validators.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[i.Validators.required]],messageAttributes:[e?e.messageAttributes:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-pub-sub-config",template:'
\n \n tb.rulenode.gcp-project-id\n \n \n {{ \'tb.rulenode.gcp-project-id-required\' | translate }}\n \n \n \n tb.rulenode.pubsub-topic-name\n \n \n {{ \'tb.rulenode.pubsub-topic-name-required\' | translate }}\n \n \n \n \n \n
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.ackValues=["all","-1","0","1"],n.ToByteStandartCharsetTypesValues=Y,n.ToByteStandartCharsetTypeTranslationMap=Z,n}return y(r,e),r.prototype.configForm=function(){return this.kafkaConfigForm},r.prototype.onConfigurationSet=function(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],bootstrapServers:[e?e.bootstrapServers:null,[i.Validators.required]],retries:[e?e.retries:null,[i.Validators.min(0)]],batchSize:[e?e.batchSize:null,[i.Validators.min(0)]],linger:[e?e.linger:null,[i.Validators.min(0)]],bufferMemory:[e?e.bufferMemory:null,[i.Validators.min(0)]],acks:[e?e.acks:null,[i.Validators.required]],keySerializer:[e?e.keySerializer:null,[i.Validators.required]],valueSerializer:[e?e.valueSerializer:null,[i.Validators.required]],otherProperties:[e?e.otherProperties:null,[]],addMetadataKeyValuesAsKafkaHeaders:[!!e&&e.addMetadataKeyValuesAsKafkaHeaders,[]],kafkaHeadersCharset:[e?e.kafkaHeadersCharset:null,[]]})},r.prototype.validatorTriggers=function(){return["addMetadataKeyValuesAsKafkaHeaders"]},r.prototype.updateValidators=function(e){this.kafkaConfigForm.get("addMetadataKeyValuesAsKafkaHeaders").value?this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([i.Validators.required]):this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([]),this.kafkaConfigForm.get("kafkaHeadersCharset").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-kafka-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n tb.rulenode.bootstrap-servers\n \n \n {{ \'tb.rulenode.bootstrap-servers-required\' | translate }}\n \n \n \n tb.rulenode.retries\n \n \n {{ \'tb.rulenode.min-retries-message\' | translate }}\n \n \n \n tb.rulenode.batch-size-bytes\n \n \n {{ \'tb.rulenode.min-batch-size-bytes-message\' | translate }}\n \n \n \n tb.rulenode.linger-ms\n \n \n {{ \'tb.rulenode.min-linger-ms-message\' | translate }}\n \n \n \n tb.rulenode.buffer-memory-bytes\n \n \n {{ \'tb.rulenode.min-buffer-memory-bytes-message\' | translate }}\n \n \n \n tb.rulenode.acks\n \n \n {{ ackValue }}\n \n \n \n \n tb.rulenode.key-serializer\n \n \n {{ \'tb.rulenode.key-serializer-required\' | translate }}\n \n \n \n tb.rulenode.value-serializer\n \n \n {{ \'tb.rulenode.value-serializer-required\' | translate }}\n \n \n \n \n \n \n {{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}\n \n
tb.rulenode.add-metadata-key-values-as-kafka-headers-hint
\n \n tb.rulenode.charset-encoding\n \n \n {{ ToByteStandartCharsetTypeTranslationMap.get(charset) | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allMqttCredentialsTypes=Q,n.mqttCredentialsTypeTranslationsMap=_,n}return y(r,e),r.prototype.configForm=function(){return this.mqttConfigForm},r.prototype.onConfigurationSet=function(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],username:[e&&e.credentials?e.credentials.username:null,[]],password:[e&&e.credentials?e.credentials.password: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,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;switch(t){case"anonymous":e.credentials={type:t};break;case"basic":e.credentials={type:t,username:e.credentials.username,password:e.credentials.password};break;case"cert.PEM":delete e.credentials.username}return e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.mqttConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("username").setValidators([]),t.get("password").setValidators([]),t.get("caCert").setValidators([]),t.get("caCertFileName").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"anonymous":break;case"basic":t.get("username").setValidators([i.Validators.required]),t.get("password").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("caCert").setValidators([i.Validators.required]),t.get("caCertFileName").setValidators([i.Validators.required]),t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("username").updateValueAndValidity({emitEvent:e}),t.get("password").updateValueAndValidity({emitEvent:e}),t.get("caCert").updateValueAndValidity({emitEvent:e}),t.get("caCertFileName").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})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-mqtt-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n \n tb.rulenode.connect-timeout\n \n \n {{ \'tb.rulenode.connect-timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n
\n \n tb.rulenode.client-id\n \n \n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n \n tb.rulenode.credentials\n \n {{ mqttCredentialsTypeTranslationsMap.get(mqttConfigForm.get(\'credentials\').get(\'type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ mqttCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.username\n \n \n {{ \'tb.rulenode.username-required\' | translate }}\n \n \n \n tb.rulenode.password\n \n \n {{ \'tb.rulenode.password-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],n}return y(r,e),r.prototype.configForm=function(){return this.rabbitMqConfigForm},r.prototype.onConfigurationSet=function(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,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.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,[i.Validators.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[i.Validators.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rabbit-mq-config",template:'
\n \n tb.rulenode.exchange-name-pattern\n \n \n \n tb.rulenode.routing-key-pattern\n \n \n \n tb.rulenode.message-properties\n \n \n {{ property }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n
\n \n tb.rulenode.virtual-host\n \n \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n {{ \'tb.rulenode.automatic-recovery\' | translate }}\n \n \n tb.rulenode.connection-timeout-ms\n \n \n {{ \'tb.rulenode.min-connection-timeout-ms-message\' | translate }}\n \n \n \n tb.rulenode.handshake-timeout-ms\n \n \n {{ \'tb.rulenode.min-handshake-timeout-ms-message\' | translate }}\n \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ge=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.proxySchemes=["http","https"],n.httpRequestTypes=Object.keys(z),n}return y(r,e),r.prototype.configForm=function(){return this.restApiCallConfigForm},r.prototype.onConfigurationSet=function(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[i.Validators.required]],requestMethod:[e?e.requestMethod:null,[i.Validators.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],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,[]],maxParallelRequestsCount:[e?e.maxParallelRequestsCount:null,[i.Validators.min(0)]],headers:[e?e.headers:null,[]],useRedisQueueForMsgPersistence:[!!e&&e.useRedisQueueForMsgPersistence,[]],trimQueue:[!!e&&e.trimQueue,[]],maxQueueSize:[e?e.maxQueueSize:null,[]]})},r.prototype.validatorTriggers=function(){return["useSimpleClientHttpFactory","useRedisQueueForMsgPersistence","enableProxy","useSystemProxyProperties"]},r.prototype.updateValidators=function(e){var t=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,r=this.restApiCallConfigForm.get("useRedisQueueForMsgPersistence").value,n=this.restApiCallConfigForm.get("enableProxy").value,a=this.restApiCallConfigForm.get("useSystemProxyProperties").value;n&&!a?(this.restApiCallConfigForm.get("proxyHost").setValidators(n?[i.Validators.required]:[]),this.restApiCallConfigForm.get("proxyPort").setValidators(n?[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]:[])):(this.restApiCallConfigForm.get("proxyHost").setValidators([]),this.restApiCallConfigForm.get("proxyPort").setValidators([]),t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([i.Validators.min(0)])),r?this.restApiCallConfigForm.get("maxQueueSize").setValidators([i.Validators.min(0)]):this.restApiCallConfigForm.get("maxQueueSize").setValidators([]),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("maxQueueSize").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rest-api-call-config",template:'
\n \n tb.rulenode.endpoint-url-pattern\n \n \n {{ \'tb.rulenode.endpoint-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.request-method\n \n \n {{ requestType }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n \n {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}\n \n
\n \n {{ \'tb.rulenode.use-system-proxy-properties\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-scheme\n \n \n {{ proxyScheme }}\n \n \n \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n
\n \n tb.rulenode.read-timeout\n \n \n \n \n tb.rulenode.max-parallel-requests-count\n \n \n \n \n
\n \n \n \n {{ \'tb.rulenode.use-redis-queue\' | translate }}\n \n
\n \n {{ \'tb.rulenode.trim-redis-queue\' | translate }}\n \n \n tb.rulenode.redis-queue-max-size\n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ye=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.smtpProtocols=["smtp","smtps"],n.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"],n}return y(r,e),r.prototype.configForm=function(){return this.sendEmailConfigForm},r.prototype.onConfigurationSet=function(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,[]]})},r.prototype.validatorTriggers=function(){return["useSystemSmtpSettings","enableProxy"]},r.prototype.updateValidators=function(e){var t=this.sendEmailConfigForm.get("useSystemSmtpSettings").value,r=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([i.Validators.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([i.Validators.required,i.Validators.min(0)]),this.sendEmailConfigForm.get("proxyHost").setValidators(r?[i.Validators.required]:[]),this.sendEmailConfigForm.get("proxyPort").setValidators(r?[i.Validators.required,i.Validators.min(1),i.Validators.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})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-send-email-config",template:'
\n \n {{ \'tb.rulenode.use-system-smtp-settings\' | translate }}\n \n
\n \n tb.rulenode.smtp-protocol\n \n \n {{ smtpProtocol.toUpperCase() }}\n \n \n \n
\n \n tb.rulenode.smtp-host\n \n \n {{ \'tb.rulenode.smtp-host-required\' | translate }}\n \n \n \n tb.rulenode.smtp-port\n \n \n {{ \'tb.rulenode.smtp-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.timeout-msec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-msec-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.enable-tls\' | translate }}\n \n \n tb.rulenode.tls-version\n \n \n {{ tlsVersion }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),be=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.serviceType=a.ServiceType.TB_RULE_ENGINE,n}return y(r,e),r.prototype.configForm=function(){return this.checkPointConfigForm},r.prototype.onConfigurationSet=function(e){this.checkPointConfigForm=this.fb.group({queueName:[e?e.queueName:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-check-point-config",template:'
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),he=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allAzureIotHubCredentialsTypes=W,n.azureIotHubCredentialsTypeTranslationsMap=J,n}return y(r,e),r.prototype.configForm=function(){return this.azureIotHubConfigForm},r.prototype.onConfigurationSet=function(e){this.azureIotHubConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[i.Validators.required]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.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,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;return"sas"===t&&(e.credentials={type:t,sasKey:e.credentials.sasKey,caCert:e.credentials.caCert,caCertFileName:e.credentials.caCertFileName}),e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.azureIotHubConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("sasKey").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"sas":t.get("sasKey").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.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})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-azure-iot-hub-config",template:'
\n \n tb.rulenode.topic\n \n \n {{ \'tb.rulenode.topic-required\' | translate }}\n \n \n \n tb.rulenode.hostname\n \n \n {{ \'tb.rulenode.hostname-required\' | translate }}\n \n \n \n tb.rulenode.device-id\n \n \n {{ \'tb.rulenode.device-id-required\' | translate }}\n \n \n \n \n \n tb.rulenode.credentials\n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(azureIotHubConfigForm.get(\'credentials.type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.sas-key\n \n \n {{ \'tb.rulenode.sas-key-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.deviceProfile},r.prototype.onConfigurationSet=function(e){this.deviceProfile=this.fb.group({persistAlarmRulesState:[!!e&&e.persistAlarmRulesState,i.Validators.required],fetchAlarmRulesStateOnStart:[!!e&&e.fetchAlarmRulesStateOnStart,i.Validators.required]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-device-profile-config",template:'
\n \n {{ \'tb.rulenode.persist-alarm-rules\' | translate }}\n \n \n {{ \'tb.rulenode.fetch-alarm-rules\' | translate }}\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ve=function(){function e(){}return e=b([t.NgModule({declarations:[x,T,q,S,I,k,N,V,E,A,L,X,ee,te,re,se,me,ue,de,pe,ce,fe,ge,ye,be,he,Ce],imports:[r.CommonModule,a.SharedModule,le],exports:[x,T,q,S,I,k,N,V,E,A,L,X,ee,te,re,se,me,ue,de,pe,ce,fe,ge,ye,be,he,Ce]})],e)}(),Fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.checkMessageConfigForm},r.prototype.onConfigurationSet=function(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]})},r.prototype.validateConfig=function(){var e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0},r.prototype.removeMessageName=function(e){var t=this.checkMessageConfigForm.get("messageNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))},r.prototype.removeMetadataName=function(e){var t=this.checkMessageConfigForm.get("metadataNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))},r.prototype.addMessageName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("messageNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("messageNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.prototype.addMetadataName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("metadataNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("metadataNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-message-config",template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
tb.rulenode.check-all-keys-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),xe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.entitySearchDirection=Object.keys(a.EntitySearchDirection),n.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.checkRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],relationType:[e?e.relationType:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["checkForSingleEntity"]},r.prototype.updateValidators=function(e){var t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-relation-config",template:'
\n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.check-relation-hint
\n \n relation.direction\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }}\n \n \n \n
\n \n \n \n \n
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=B,n}return y(r,e),r.prototype.configForm=function(){return this.geoFilterConfigForm},r.prototype.onConfigurationSet=function(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType: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,[]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterType").setValidators([]):this.geoFilterConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoFilterConfigForm.get("centerLatitude").setValidators([]),this.geoFilterConfigForm.get("centerLongitude").setValidators([]),this.geoFilterConfigForm.get("range").setValidators([]),this.geoFilterConfigForm.get("rangeUnit").setValidators([])):(this.geoFilterConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoFilterConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoFilterConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),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})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),qe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.messageTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e?e.messageTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-message-type-config",template:'
\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allowedEntityTypes=[a.EntityType.DEVICE,a.EntityType.ASSET,a.EntityType.ENTITY_VIEW,a.EntityType.TENANT,a.EntityType.CUSTOMER,a.EntityType.USER,a.EntityType.DASHBOARD,a.EntityType.RULE_CHAIN,a.EntityType.RULE_NODE],n}return y(r,e),r.prototype.configForm=function(){return this.originatorTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-originator-type-config",template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ie=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),ke=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.switchConfigForm},r.prototype.onConfigurationSet=function(e){this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.switchConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ne=function(e){function r(t,r,n){var o,l,s=e.call(this,t)||this;s.store=t,s.translate=r,s.fb=n,s.alarmStatusTranslationsMap=a.alarmStatusTranslations,s.alarmStatusList=[],s.searchText="",s.displayStatusFn=s.displayStatus.bind(s);try{for(var m=C(Object.keys(a.AlarmStatus)),u=m.next();!u.done;u=m.next()){var d=u.value;s.alarmStatusList.push(a.AlarmStatus[d])}}catch(e){o={error:e}}finally{try{u&&!u.done&&(l=m.return)&&l.call(m)}finally{if(o)throw o.error}}return s.statusFormControl=new i.FormControl(""),s.filteredAlarmStatus=s.statusFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return s.fetchAlarmStatus(e)})),f.share()),s}return y(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.alarmStatusConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.statusFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.alarmStatusConfigForm=this.fb.group({alarmStatusList:[e?e.alarmStatusList:null,[i.Validators.required]]})},r.prototype.displayStatus=function(e){return e?this.translate.instant(a.alarmStatusTranslations.get(e)):void 0},r.prototype.fetchAlarmStatus=function(e){var t=this,r=this.getAlarmStatusList();if(this.searchText=e,this.searchText&&this.searchText.length){var n=this.searchText.toUpperCase();return c.of(r.filter((function(e){return t.translate.instant(a.alarmStatusTranslations.get(a.AlarmStatus[e])).toUpperCase().includes(n)})))}return c.of(r)},r.prototype.alarmStatusSelected=function(e){this.addAlarmStatus(e.option.value),this.clear("")},r.prototype.removeAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))}},r.prototype.addAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))},r.prototype.getAlarmStatusList=function(){var e=this;return this.alarmStatusList.filter((function(t){return-1===e.alarmStatusConfigForm.get("alarmStatusList").value.indexOf(t)}))},r.prototype.onAlarmStatusInputFocus=function(){this.statusFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.alarmStatusInput.nativeElement.value=e,this.statusFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.alarmStatusInput.nativeElement.blur(),t.alarmStatusInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("alarmStatusInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"alarmStatusInput",void 0),r=b([t.Component({selector:"tb-filter-node-check-alarm-status-config",template:'
\n \n tb.rulenode.alarm-status-filter\n \n \n \n {{alarmStatusTranslationsMap.get(alarmStatus) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-alarm-status-matching\n
\n
\n
\n
\n
\n \n
\n\n\n\n'}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ve=function(){function e(){}return e=b([t.NgModule({declarations:[Fe,xe,Te,qe,Se,Ie,ke,Ne],imports:[r.CommonModule,a.SharedModule,le],exports:[Fe,xe,Te,qe,Se,Ie,ke,Ne]})],e)}(),Ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.customerAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ae=function(e){function r(t,r,n){var a,o,l=e.call(this,t)||this;l.store=t,l.translate=r,l.fb=n,l.entityDetailsTranslationsMap=G,l.entityDetailsList=[],l.searchText="",l.displayDetailsFn=l.displayDetails.bind(l);try{for(var s=C(Object.keys(K)),m=s.next();!m.done;m=s.next()){var u=m.value;l.entityDetailsList.push(K[u])}}catch(e){a={error:e}}finally{try{m&&!m.done&&(o=s.return)&&o.call(s)}finally{if(a)throw a.error}}return l.detailsFormControl=new i.FormControl(""),l.filteredEntityDetails=l.detailsFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return l.fetchEntityDetails(e)})),f.share()),l}return y(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.entityDetailsConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.detailsFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[i.Validators.required]],addToMetadata:[!!e&&e.addToMetadata,[]]})},r.prototype.displayDetails=function(e){return e?this.translate.instant(G.get(e)):void 0},r.prototype.fetchEntityDetails=function(e){var t=this;if(this.searchText=e,this.searchText&&this.searchText.length){var r=this.searchText.toUpperCase();return c.of(this.entityDetailsList.filter((function(e){return t.translate.instant(G.get(K[e])).toUpperCase().includes(r)})))}return c.of(this.entityDetailsList)},r.prototype.detailsFieldSelected=function(e){this.addDetailsField(e.option.value),this.clear("")},r.prototype.removeDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}},r.prototype.addDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))},r.prototype.onEntityDetailsInputFocus=function(){this.detailsFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.detailsInput.nativeElement.blur(),t.detailsInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("detailsInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"detailsInput",void 0),r=b([t.Component({selector:"tb-enrichment-node-entity-details-config",template:'
\n \n tb.rulenode.entity-details\n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}"]}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Le=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.deviceAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[i.Validators.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.deviceAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.deviceAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.deviceAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.deviceAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-device-attributes-config",template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.originatorAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.originatorAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.originatorAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.originatorAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.originatorAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-attributes-config",template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.originatorFieldsConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n.fetchMode=U,n.fetchModes=Object.keys(U),n.samplingOrders=Object.keys(j),n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=D,n}return y(r,e),r.prototype.configForm=function(){return this.getTelemetryFromDatabaseConfigForm},r.prototype.onConfigurationSet=function(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],fetchMode:[e?e.fetchMode:null,[i.Validators.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchMode","useMetadataIntervalPatterns"]},r.prototype.updateValidators=function(e){var t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,r=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===U.ALL?(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([i.Validators.required,i.Validators.min(2),i.Validators.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),r?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([i.Validators.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})},r.prototype.removeKey=function(e,t){var r=this.getTelemetryFromDatabaseConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.getTelemetryFromDatabaseConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\n \n \n {{ \'tb.rulenode.start-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\n \n \n {{ \'tb.rulenode.end-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),we=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.relatedAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e?e.relationsQuery:null,[i.Validators.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.tenantAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),De=function(){function e(){}return e=b([t.NgModule({declarations:[Ee,Ae,Le,Me,Pe,Re,we,Oe],imports:[r.CommonModule,a.SharedModule,le],exports:[Ee,Ae,Le,Me,Pe,Re,we,Oe]})],e)}(),Ke=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.originatorSource=v,n.originatorSources=Object.keys(v),n.originatorSourceTranslationMap=P,n}return y(r,e),r.prototype.configForm=function(){return this.changeOriginatorConfigForm},r.prototype.onConfigurationSet=function(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[i.Validators.required]],relationsQuery:[e?e.relationsQuery:null,[]]})},r.prototype.validatorTriggers=function(){return["originatorSource"]},r.prototype.updateValidators=function(e){var t=this.changeOriginatorConfigForm.get("originatorSource").value;t&&t===v.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([i.Validators.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-transformation-node-change-originator-config",template:'
\n \n tb.rulenode.originator-source\n \n \n {{ originatorSourceTranslationMap.get(source) | translate }}\n \n \n \n
\n \n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Be=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-transformation-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ue=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.toEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[i.Validators.required]],toTemplate:[e?e.toTemplate:null,[i.Validators.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[i.Validators.required]],bodyTemplate:[e?e.bodyTemplate:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-transformation-node-to-email-config",template:'
\n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n \n \n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.cc-template\n \n \n \n \n tb.rulenode.bcc-template\n \n \n \n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),je=function(){function e(){}return e=b([t.NgModule({declarations:[Ke,Be,Ue],imports:[r.CommonModule,a.SharedModule,le],exports:[Ke,Be,Ue]})],e)}(),He=function(){function e(e){!function(e){e.setTranslation("en_US",{tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","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 name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","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.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","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","client-attributes-hint":"Client attributes, use ${metaKeyName} to substitute variables from metadata","shared-attributes":"Shared attributes","shared-attributes-hint":"Shared attributes, use ${metaKeyName} to substitute variables from metadata","server-attributes":"Server attributes","server-attributes-hint":"Server attributes, use ${metaKeyName} to substitute variables from metadata","notify-device":"Notify Device","notify-device-hint":"If the message arrives from the device, we will push it back to the device by default.","latest-timeseries":"Latest timeseries","latest-timeseries-hint":"Latest timeseries, use ${metaKeyName} to substitute variables from metadata","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","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","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","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","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required","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",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","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","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","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",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata",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.",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","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","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","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","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 ${metaKeyName} in name/value fields to substitute variables from metadata","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","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-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":"CA certificate file *","private-key":"Private key file *",cert:"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-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys 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-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","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-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","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","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.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","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","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details 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":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","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:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, 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 Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size","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.","persist-alarm-rules":"Persist state of alarm rules","fetch-alarm-rules":"Fetch state of alarm rules"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.ctorParameters=function(){return[{type:n.TranslateService}]},e=b([t.NgModule({declarations:[F],imports:[r.CommonModule,a.SharedModule],exports:[ve,Ve,De,je,F]}),h("design:paramtypes",[n.TranslateService])],e)}();e.RuleNodeCoreConfigModule=He,e.ɵa=F,e.ɵb=ve,e.ɵba=be,e.ɵbb=he,e.ɵbc=Ce,e.ɵbd=le,e.ɵbe=ne,e.ɵbf=ae,e.ɵbg=oe,e.ɵbh=ie,e.ɵbi=Ve,e.ɵbj=Fe,e.ɵbk=xe,e.ɵbl=Te,e.ɵbm=qe,e.ɵbn=Se,e.ɵbo=Ie,e.ɵbp=ke,e.ɵbq=Ne,e.ɵbr=De,e.ɵbs=Ee,e.ɵbt=Ae,e.ɵbu=Le,e.ɵbv=Me,e.ɵbw=Pe,e.ɵbx=Re,e.ɵby=we,e.ɵbz=Oe,e.ɵc=x,e.ɵca=je,e.ɵcb=Ke,e.ɵcc=Be,e.ɵcd=Ue,e.ɵd=T,e.ɵe=q,e.ɵf=S,e.ɵg=I,e.ɵh=k,e.ɵi=N,e.ɵj=V,e.ɵk=E,e.ɵl=A,e.ɵm=L,e.ɵn=X,e.ɵo=ee,e.ɵp=te,e.ɵq=re,e.ɵr=se,e.ɵs=me,e.ɵt=ue,e.ɵu=de,e.ɵv=pe,e.ɵw=ce,e.ɵx=fe,e.ɵy=ge,e.ɵz=ye,Object.defineProperty(e,"__esModule",{value:!0})})); + ***************************************************************************** */var g=function(e,t){return(g=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])})(e,t)};function y(e,t){function r(){this.constructor=e}g(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}function b(e,t,r,n){var a,o=arguments.length,i=o<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,r):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,r,n);else for(var l=e.length-1;l>=0;l--)(a=e[l])&&(i=(o<3?a(i):o>3?a(t,r,i):a(t,r))||i);return o>3&&i&&Object.defineProperty(t,r,i),i}function h(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}Object.create;function C(e){var t="function"==typeof Symbol&&Symbol.iterator,r=t&&e[t],n=0;if(r)return r.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&n>=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}Object.create;var v,F=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.emptyConfigForm},r.prototype.onConfigurationSet=function(e){this.emptyConfigForm=this.fb.group({})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-node-empty-config",template:"
"}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),x=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.attributeScopes=Object.keys(a.AttributeScope),n.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.attributesConfigForm},r.prototype.onConfigurationSet=function(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[i.Validators.required]],notifyDevice:[!e||e.scope,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-attributes-config",template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
tb.rulenode.notify-device-hint
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),T=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.timeseriesConfigForm},r.prototype.onConfigurationSet=function(e){this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-timeseries-config",template:'
\n \n tb.rulenode.default-ttl\n \n \n {{ \'tb.rulenode.default-ttl-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-default-ttl-message\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),q=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcRequestConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rpc-request-config",template:'
\n \n tb.rulenode.timeout-sec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-message\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),S=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.logConfigForm},r.prototype.onConfigurationSet=function(e){this.logConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.logConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.logConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),I=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.assignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.create-customer-if-not-exists\' | translate }}\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),k=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.clearAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.clearAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],alarmType:[e?e.alarmType:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.clearAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-clear-alarm-config",template:'
\n \n \n \n
\n \n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),N=function(e){function r(t,r,n,o){var i=e.call(this,t)||this;return i.store=t,i.fb=r,i.nodeScriptTestService=n,i.translate=o,i.alarmSeverities=Object.keys(a.AlarmSeverity),i.alarmSeverityTranslationMap=a.alarmSeverityTranslations,i.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],i}return y(r,e),r.prototype.configForm=function(){return this.createAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.createAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]]})},r.prototype.validatorTriggers=function(){return["useMessageAlarmData"]},r.prototype.updateValidators=function(e){this.createAlarmConfigForm.get("useMessageAlarmData").value?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([i.Validators.required]),this.createAlarmConfigForm.get("severity").setValidators([i.Validators.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e})},r.prototype.testScript=function(){var e=this,t=this.createAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.createAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.removeKey=function(e,t){var r=this.createAlarmConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.createAlarmConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.createAlarmConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.createAlarmConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-create-alarm-config",template:'
\n \n \n \n
\n \n
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n
\n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n \n tb.rulenode.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n
\n \n {{ \'tb.rulenode.propagate\' | translate }}\n \n
\n \n tb.rulenode.relation-types-list\n \n \n {{key}}\n close\n \n \n \n \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),V=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.createRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[i.Validators.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["entityType"]},r.prototype.updateValidators=function(e){var t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==a.EntityType.DEVICE&&t!==a.EntityType.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([i.Validators.required]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-create-relation-config",template:'
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.create-entity-if-not-exists\' | translate }}\n \n
tb.rulenode.create-entity-if-not-exists-hint
\n
\n \n {{ \'tb.rulenode.remove-current-relations\' | translate }}\n \n
tb.rulenode.remove-current-relations-hint
\n \n {{ \'tb.rulenode.change-originator-to-related-entity\' | translate }}\n \n
tb.rulenode.change-originator-to-related-entity-hint
\n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),E=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgDelayConfigForm},r.prototype.onConfigurationSet=function(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,[i.Validators.required,i.Validators.min(1),i.Validators.max(1e5)]]})},r.prototype.validatorTriggers=function(){return["useMetadataPeriodInSecondsPatterns"]},r.prototype.updateValidators=function(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([i.Validators.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([i.Validators.required,i.Validators.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-msg-delay-config",template:'
\n \n {{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
\n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-0-seconds-message\' | translate }}\n \n \n \n \n tb.rulenode.period-in-seconds-pattern\n \n \n {{ \'tb.rulenode.period-in-seconds-pattern-required\' | translate }}\n \n \n \n \n \n tb.rulenode.max-pending-messages\n \n \n {{ \'tb.rulenode.max-pending-messages-required\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),A=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.deleteRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["deleteForSingleEntity","entityType"]},r.prototype.updateValidators=function(e){var t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,r=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&r?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-delete-relation-config",template:'
\n \n {{ \'tb.rulenode.delete-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.delete-relation-hint
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),L=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.generatorConfigForm},r.prototype.onConfigurationSet=function(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[i.Validators.required,i.Validators.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[i.Validators.required,i.Validators.min(1)]],originator:[e?e.originator:null,[]],jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.prepareInputConfig=function(e){return e&&(e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e},r.prototype.prepareOutputConfig=function(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e},r.prototype.testScript=function(){var e=this,t=this.generatorConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId).subscribe((function(t){t&&e.generatorConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-generator-config",template:'
\n \n tb.rulenode.message-count\n \n \n {{ \'tb.rulenode.message-count-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-message-count-message\' | translate }}\n \n \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-seconds-message\' | translate }}\n \n \n
\n \n \n \n
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent);!function(e){e.CUSTOMER="CUSTOMER",e.TENANT="TENANT",e.RELATED="RELATED",e.ALARM_ORIGINATOR="ALARM_ORIGINATOR"}(v||(v={}));var M,P=new Map([[v.CUSTOMER,"tb.rulenode.originator-customer"],[v.TENANT,"tb.rulenode.originator-tenant"],[v.RELATED,"tb.rulenode.originator-related"],[v.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]);!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(M||(M={}));var R,w=new Map([[M.CIRCLE,"tb.rulenode.perimeter-circle"],[M.POLYGON,"tb.rulenode.perimeter-polygon"]]);!function(e){e.MILLISECONDS="MILLISECONDS",e.SECONDS="SECONDS",e.MINUTES="MINUTES",e.HOURS="HOURS",e.DAYS="DAYS"}(R||(R={}));var O,D=new Map([[R.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[R.SECONDS,"tb.rulenode.time-unit-seconds"],[R.MINUTES,"tb.rulenode.time-unit-minutes"],[R.HOURS,"tb.rulenode.time-unit-hours"],[R.DAYS,"tb.rulenode.time-unit-days"]]);!function(e){e.METER="METER",e.KILOMETER="KILOMETER",e.FOOT="FOOT",e.MILE="MILE",e.NAUTICAL_MILE="NAUTICAL_MILE"}(O||(O={}));var K,B=new Map([[O.METER,"tb.rulenode.range-unit-meter"],[O.KILOMETER,"tb.rulenode.range-unit-kilometer"],[O.FOOT,"tb.rulenode.range-unit-foot"],[O.MILE,"tb.rulenode.range-unit-mile"],[O.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);!function(e){e.TITLE="TITLE",e.COUNTRY="COUNTRY",e.STATE="STATE",e.ZIP="ZIP",e.ADDRESS="ADDRESS",e.ADDRESS2="ADDRESS2",e.PHONE="PHONE",e.EMAIL="EMAIL",e.ADDITIONAL_INFO="ADDITIONAL_INFO"}(K||(K={}));var U,j,H,G=new Map([[K.TITLE,"tb.rulenode.entity-details-title"],[K.COUNTRY,"tb.rulenode.entity-details-country"],[K.STATE,"tb.rulenode.entity-details-state"],[K.ZIP,"tb.rulenode.entity-details-zip"],[K.ADDRESS,"tb.rulenode.entity-details-address"],[K.ADDRESS2,"tb.rulenode.entity-details-address2"],[K.PHONE,"tb.rulenode.entity-details-phone"],[K.EMAIL,"tb.rulenode.entity-details-email"],[K.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(U||(U={})),function(e){e.ASC="ASC",e.DESC="DESC"}(j||(j={})),function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(H||(H={}));var z,$=new Map([[H.STANDARD,"tb.rulenode.sqs-queue-standard"],[H.FIFO,"tb.rulenode.sqs-queue-fifo"]]),Q=["anonymous","basic","cert.PEM"],_=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),W=["sas","cert.PEM"],J=new Map([["sas","tb.rulenode.credentials-sas"],["cert.PEM","tb.rulenode.credentials-pem"]]);!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE"}(z||(z={}));var Y=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],Z=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"]]),X=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=B,n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=D,n}return y(r,e),r.prototype.configForm=function(){return this.geoActionConfigForm},r.prototype.onConfigurationSet=function(e){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType: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,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[i.Validators.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterType").setValidators([]):this.geoActionConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoActionConfigForm.get("centerLatitude").setValidators([]),this.geoActionConfigForm.get("centerLongitude").setValidators([]),this.geoActionConfigForm.get("range").setValidators([]),this.geoActionConfigForm.get("rangeUnit").setValidators([])):(this.geoActionConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoActionConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoActionConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),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})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.min-inside-duration\n \n \n {{ \'tb.rulenode.min-inside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-inside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.min-outside-duration\n \n \n {{ \'tb.rulenode.min-outside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-outside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgCountConfigForm},r.prototype.onConfigurationSet=function(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[i.Validators.required,i.Validators.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-msg-count-config",template:'
\n \n tb.rulenode.interval-seconds\n \n \n {{ \'tb.rulenode.interval-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-interval-seconds-message\' | translate }}\n \n \n \n tb.rulenode.output-timeseries-key-prefix\n \n \n {{ \'tb.rulenode.output-timeseries-key-prefix-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcReplyConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rpc-reply-config",template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.saveToCustomTableConfigForm},r.prototype.onConfigurationSet=function(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[i.Validators.required]],fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-custom-table-config",template:'
\n \n tb.rulenode.custom-table-name\n \n \n {{ \'tb.rulenode.custom-table-name-required\' | translate }}\n \n \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ne=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.translate=r,o.injector=n,o.fb=a,o.propagateChange=null,o.valueChangeSubscription=null,o}var a;return y(r,e),a=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){this.ngControl=this.injector.get(i.NgControl),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))},r.prototype.keyValsFormArray=function(){return this.kvListFormGroup.get("keyVals")},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t,r,n=this;this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();var a=[];if(e)try{for(var o=C(Object.keys(e)),l=o.next();!l.done;l=o.next()){var s=l.value;Object.prototype.hasOwnProperty.call(e,s)&&a.push(this.fb.group({key:[s,[i.Validators.required]],value:[e[s],[i.Validators.required]]}))}}catch(e){t={error:e}}finally{try{l&&!l.done&&(r=o.return)&&r.call(o)}finally{if(t)throw t.error}}this.kvListFormGroup.setControl("keyVals",this.fb.array(a)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((function(){n.updateModel()}))},r.prototype.removeKeyVal=function(e){this.kvListFormGroup.get("keyVals").removeAt(e)},r.prototype.addKeyVal=function(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[i.Validators.required]],value:["",[i.Validators.required]]}))},r.prototype.validate=function(e){return!this.kvListFormGroup.get("keyVals").value.length&&this.required?{kvMapRequired:!0}:this.kvListFormGroup.valid?null:{kvFieldsRequired:!0}},r.prototype.updateModel=function(){var e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{var t={};e.forEach((function(e){t[e.key]=e.value})),this.propagateChange(t)}},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:t.Injector},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",String)],r.prototype,"requiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyRequiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valRequiredText",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=a=b([t.Component({selector:"tb-kv-map-config",template:'
\n
\n {{ keyText }}\n {{ valText }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n \n
\n \n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return a})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return a})),multi:!0}],styles:[":host .tb-kv-map-config{margin-bottom:16px}:host .tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}:host .tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .row{padding-top:5px;max-height:40px}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0;max-height:40px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0}"]}),h("design:paramtypes",[o.Store,n.TranslateService,t.Injector,i.FormBuilder])],r)}(a.PageComponent),ae=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.deviceRelationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[i.Validators.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((function(t){e.deviceRelationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-device-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((function(t){e.relationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.relationsQueryFormGroup.reset(e||{},{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-filters
\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),ie=function(e){function r(t,r,n,o){var i,l,s=e.call(this,t)||this;s.store=t,s.translate=r,s.truncate=n,s.fb=o,s.placeholder="tb.rulenode.message-type",s.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],s.messageTypes=[],s.messageTypesList=[],s.searchText="",s.propagateChange=function(e){},s.messageTypeConfigForm=s.fb.group({messageType:[null]});try{for(var u=C(Object.keys(a.MessageType)),d=u.next();!d.done;d=u.next()){var p=d.value;s.messageTypesList.push({name:a.messageTypeNames.get(a.MessageType[p]),value:p})}}catch(e){i={error:e}}finally{try{d&&!d.done&&(l=u.return)&&l.call(u)}finally{if(i)throw i.error}}return s}var l;return y(r,e),l=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.ngOnInit=function(){var e=this;this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(t){return e.fetchMessageTypes(t)})),f.share())},r.prototype.ngAfterViewInit=function(){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t=this;this.searchText="",this.messageTypes.length=0,e&&e.forEach((function(e){var r=t.messageTypesList.find((function(t){return t.value===e}));r?t.messageTypes.push({name:r.name,value:r.value}):t.messageTypes.push({name:e,value:e})}))},r.prototype.displayMessageTypeFn=function(e){return e?e.name:void 0},r.prototype.textIsNotEmpty=function(e){return!!(e&&null!=e&&e.length>0)},r.prototype.createMessageType=function(e,t){e.preventDefault(),this.transformMessageType(t)},r.prototype.add=function(e){this.transformMessageType(e.value)},r.prototype.fetchMessageTypes=function(e){if(this.searchText=e,this.searchText&&this.searchText.length){var t=this.searchText.toUpperCase();return c.of(this.messageTypesList.filter((function(e){return e.name.toUpperCase().includes(t)})))}return c.of(this.messageTypesList)},r.prototype.transformMessageType=function(e){if((e||"").trim()){var t=null,r=e.trim(),n=this.messageTypesList.find((function(e){return e.name===r}));(t=n?{name:n.name,value:n.value}:{name:r,value:r})&&this.addMessageType(t)}this.clear("")},r.prototype.remove=function(e){var t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())},r.prototype.selected=function(e){this.addMessageType(e.option.value),this.clear("")},r.prototype.addMessageType=function(e){-1===this.messageTypes.findIndex((function(t){return t.value===e.value}))&&(this.messageTypes.push(e),this.updateModel())},r.prototype.onFocus=function(){this.messageTypeConfigForm.get("messageType").updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((function(){t.messageTypeInput.nativeElement.blur(),t.messageTypeInput.nativeElement.focus()}),0)},r.prototype.updateModel=function(){var e=this.messageTypes.map((function(e){return e.value}));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:a.TruncatePipe},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),b([t.Input(),h("design:type",String)],r.prototype,"label",void 0),b([t.Input(),h("design:type",Object)],r.prototype,"placeholder",void 0),b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.ViewChild("chipList",{static:!1}),h("design:type",d.MatChipList)],r.prototype,"chipList",void 0),b([t.ViewChild("messageTypeAutocomplete",{static:!1}),h("design:type",p.MatAutocomplete)],r.prototype,"matAutocomplete",void 0),b([t.ViewChild("messageTypeInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"messageTypeInput",void 0),r=l=b([t.Component({selector:"tb-message-types-config",template:'\n {{ label }}\n \n \n {{messageType.name}}\n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-message-types-found\n
\n \n \n {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\n \n \n \n tb.rulenode.create-new-message-type\n \n
\n
\n
\n \n {{ \'tb.rulenode.message-types-required\' | translate }}\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return l})),multi:!0}]}),h("design:paramtypes",[o.Store,n.TranslateService,a.TruncatePipe,i.FormBuilder])],r)}(a.PageComponent),le=function(){function e(){}return e=b([t.NgModule({declarations:[ne,ae,oe,ie],imports:[r.CommonModule,a.SharedModule,l.HomeComponentsModule],exports:[ne,ae,oe,ie]})],e)}(),se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.unassignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-un-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.snsConfigForm},r.prototype.onConfigurationSet=function(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[i.Validators.required]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-sns-config",template:'
\n \n tb.rulenode.topic-arn-pattern\n \n \n {{ \'tb.rulenode.topic-arn-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ue=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.sqsQueueType=H,n.sqsQueueTypes=Object.keys(H),n.sqsQueueTypeTranslationsMap=$,n}return y(r,e),r.prototype.configForm=function(){return this.sqsConfigForm},r.prototype.onConfigurationSet=function(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[i.Validators.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[i.Validators.required]],delaySeconds:[e?e.delaySeconds:null,[i.Validators.min(0),i.Validators.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-sqs-config",template:'
\n \n tb.rulenode.queue-type\n \n \n {{ sqsQueueTypeTranslationsMap.get(type) | translate }}\n \n \n \n \n tb.rulenode.queue-url-pattern\n \n \n {{ \'tb.rulenode.queue-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.delay-seconds\n \n \n {{ \'tb.rulenode.min-delay-seconds-message\' | translate }}\n \n \n {{ \'tb.rulenode.max-delay-seconds-message\' | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),de=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.pubSubConfigForm},r.prototype.onConfigurationSet=function(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[i.Validators.required]],topicName:[e?e.topicName:null,[i.Validators.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[i.Validators.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[i.Validators.required]],messageAttributes:[e?e.messageAttributes:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-pub-sub-config",template:'
\n \n tb.rulenode.gcp-project-id\n \n \n {{ \'tb.rulenode.gcp-project-id-required\' | translate }}\n \n \n \n tb.rulenode.pubsub-topic-name\n \n \n {{ \'tb.rulenode.pubsub-topic-name-required\' | translate }}\n \n \n \n \n \n
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.ackValues=["all","-1","0","1"],n.ToByteStandartCharsetTypesValues=Y,n.ToByteStandartCharsetTypeTranslationMap=Z,n}return y(r,e),r.prototype.configForm=function(){return this.kafkaConfigForm},r.prototype.onConfigurationSet=function(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],bootstrapServers:[e?e.bootstrapServers:null,[i.Validators.required]],retries:[e?e.retries:null,[i.Validators.min(0)]],batchSize:[e?e.batchSize:null,[i.Validators.min(0)]],linger:[e?e.linger:null,[i.Validators.min(0)]],bufferMemory:[e?e.bufferMemory:null,[i.Validators.min(0)]],acks:[e?e.acks:null,[i.Validators.required]],keySerializer:[e?e.keySerializer:null,[i.Validators.required]],valueSerializer:[e?e.valueSerializer:null,[i.Validators.required]],otherProperties:[e?e.otherProperties:null,[]],addMetadataKeyValuesAsKafkaHeaders:[!!e&&e.addMetadataKeyValuesAsKafkaHeaders,[]],kafkaHeadersCharset:[e?e.kafkaHeadersCharset:null,[]]})},r.prototype.validatorTriggers=function(){return["addMetadataKeyValuesAsKafkaHeaders"]},r.prototype.updateValidators=function(e){this.kafkaConfigForm.get("addMetadataKeyValuesAsKafkaHeaders").value?this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([i.Validators.required]):this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([]),this.kafkaConfigForm.get("kafkaHeadersCharset").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-kafka-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n tb.rulenode.bootstrap-servers\n \n \n {{ \'tb.rulenode.bootstrap-servers-required\' | translate }}\n \n \n \n tb.rulenode.retries\n \n \n {{ \'tb.rulenode.min-retries-message\' | translate }}\n \n \n \n tb.rulenode.batch-size-bytes\n \n \n {{ \'tb.rulenode.min-batch-size-bytes-message\' | translate }}\n \n \n \n tb.rulenode.linger-ms\n \n \n {{ \'tb.rulenode.min-linger-ms-message\' | translate }}\n \n \n \n tb.rulenode.buffer-memory-bytes\n \n \n {{ \'tb.rulenode.min-buffer-memory-bytes-message\' | translate }}\n \n \n \n tb.rulenode.acks\n \n \n {{ ackValue }}\n \n \n \n \n tb.rulenode.key-serializer\n \n \n {{ \'tb.rulenode.key-serializer-required\' | translate }}\n \n \n \n tb.rulenode.value-serializer\n \n \n {{ \'tb.rulenode.value-serializer-required\' | translate }}\n \n \n \n \n \n \n {{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}\n \n
tb.rulenode.add-metadata-key-values-as-kafka-headers-hint
\n \n tb.rulenode.charset-encoding\n \n \n {{ ToByteStandartCharsetTypeTranslationMap.get(charset) | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allMqttCredentialsTypes=Q,n.mqttCredentialsTypeTranslationsMap=_,n}return y(r,e),r.prototype.configForm=function(){return this.mqttConfigForm},r.prototype.onConfigurationSet=function(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],username:[e&&e.credentials?e.credentials.username:null,[]],password:[e&&e.credentials?e.credentials.password: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,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;switch(t){case"anonymous":e.credentials={type:t};break;case"basic":e.credentials={type:t,username:e.credentials.username,password:e.credentials.password};break;case"cert.PEM":delete e.credentials.username}return e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.mqttConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("username").setValidators([]),t.get("password").setValidators([]),t.get("caCert").setValidators([]),t.get("caCertFileName").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"anonymous":break;case"basic":t.get("username").setValidators([i.Validators.required]),t.get("password").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("caCert").setValidators([i.Validators.required]),t.get("caCertFileName").setValidators([i.Validators.required]),t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("username").updateValueAndValidity({emitEvent:e}),t.get("password").updateValueAndValidity({emitEvent:e}),t.get("caCert").updateValueAndValidity({emitEvent:e}),t.get("caCertFileName").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})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-mqtt-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n \n tb.rulenode.connect-timeout\n \n \n {{ \'tb.rulenode.connect-timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n
\n \n tb.rulenode.client-id\n \n \n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n \n tb.rulenode.credentials\n \n {{ mqttCredentialsTypeTranslationsMap.get(mqttConfigForm.get(\'credentials\').get(\'type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ mqttCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.username\n \n \n {{ \'tb.rulenode.username-required\' | translate }}\n \n \n \n tb.rulenode.password\n \n \n {{ \'tb.rulenode.password-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],n}return y(r,e),r.prototype.configForm=function(){return this.rabbitMqConfigForm},r.prototype.onConfigurationSet=function(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,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.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,[i.Validators.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[i.Validators.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rabbit-mq-config",template:'
\n \n tb.rulenode.exchange-name-pattern\n \n \n \n tb.rulenode.routing-key-pattern\n \n \n \n tb.rulenode.message-properties\n \n \n {{ property }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n
\n \n tb.rulenode.virtual-host\n \n \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n {{ \'tb.rulenode.automatic-recovery\' | translate }}\n \n \n tb.rulenode.connection-timeout-ms\n \n \n {{ \'tb.rulenode.min-connection-timeout-ms-message\' | translate }}\n \n \n \n tb.rulenode.handshake-timeout-ms\n \n \n {{ \'tb.rulenode.min-handshake-timeout-ms-message\' | translate }}\n \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ge=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.proxySchemes=["http","https"],n.httpRequestTypes=Object.keys(z),n}return y(r,e),r.prototype.configForm=function(){return this.restApiCallConfigForm},r.prototype.onConfigurationSet=function(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[i.Validators.required]],requestMethod:[e?e.requestMethod:null,[i.Validators.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],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,[]],maxParallelRequestsCount:[e?e.maxParallelRequestsCount:null,[i.Validators.min(0)]],headers:[e?e.headers:null,[]],useRedisQueueForMsgPersistence:[!!e&&e.useRedisQueueForMsgPersistence,[]],trimQueue:[!!e&&e.trimQueue,[]],maxQueueSize:[e?e.maxQueueSize:null,[]]})},r.prototype.validatorTriggers=function(){return["useSimpleClientHttpFactory","useRedisQueueForMsgPersistence","enableProxy","useSystemProxyProperties"]},r.prototype.updateValidators=function(e){var t=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,r=this.restApiCallConfigForm.get("useRedisQueueForMsgPersistence").value,n=this.restApiCallConfigForm.get("enableProxy").value,a=this.restApiCallConfigForm.get("useSystemProxyProperties").value;n&&!a?(this.restApiCallConfigForm.get("proxyHost").setValidators(n?[i.Validators.required]:[]),this.restApiCallConfigForm.get("proxyPort").setValidators(n?[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]:[])):(this.restApiCallConfigForm.get("proxyHost").setValidators([]),this.restApiCallConfigForm.get("proxyPort").setValidators([]),t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([i.Validators.min(0)])),r?this.restApiCallConfigForm.get("maxQueueSize").setValidators([i.Validators.min(0)]):this.restApiCallConfigForm.get("maxQueueSize").setValidators([]),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("maxQueueSize").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rest-api-call-config",template:'
\n \n tb.rulenode.endpoint-url-pattern\n \n \n {{ \'tb.rulenode.endpoint-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.request-method\n \n \n {{ requestType }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n \n {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}\n \n
\n \n {{ \'tb.rulenode.use-system-proxy-properties\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-scheme\n \n \n {{ proxyScheme }}\n \n \n \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n
\n \n tb.rulenode.read-timeout\n \n \n \n \n tb.rulenode.max-parallel-requests-count\n \n \n \n \n
\n \n \n \n {{ \'tb.rulenode.use-redis-queue\' | translate }}\n \n
\n \n {{ \'tb.rulenode.trim-redis-queue\' | translate }}\n \n \n tb.rulenode.redis-queue-max-size\n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ye=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.smtpProtocols=["smtp","smtps"],n.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"],n}return y(r,e),r.prototype.configForm=function(){return this.sendEmailConfigForm},r.prototype.onConfigurationSet=function(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,[]]})},r.prototype.validatorTriggers=function(){return["useSystemSmtpSettings","enableProxy"]},r.prototype.updateValidators=function(e){var t=this.sendEmailConfigForm.get("useSystemSmtpSettings").value,r=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([i.Validators.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([i.Validators.required,i.Validators.min(0)]),this.sendEmailConfigForm.get("proxyHost").setValidators(r?[i.Validators.required]:[]),this.sendEmailConfigForm.get("proxyPort").setValidators(r?[i.Validators.required,i.Validators.min(1),i.Validators.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})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-send-email-config",template:'
\n \n {{ \'tb.rulenode.use-system-smtp-settings\' | translate }}\n \n
\n \n tb.rulenode.smtp-protocol\n \n \n {{ smtpProtocol.toUpperCase() }}\n \n \n \n
\n \n tb.rulenode.smtp-host\n \n \n {{ \'tb.rulenode.smtp-host-required\' | translate }}\n \n \n \n tb.rulenode.smtp-port\n \n \n {{ \'tb.rulenode.smtp-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.timeout-msec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-msec-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.enable-tls\' | translate }}\n \n \n tb.rulenode.tls-version\n \n \n {{ tlsVersion }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),be=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.serviceType=a.ServiceType.TB_RULE_ENGINE,n}return y(r,e),r.prototype.configForm=function(){return this.checkPointConfigForm},r.prototype.onConfigurationSet=function(e){this.checkPointConfigForm=this.fb.group({queueName:[e?e.queueName:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-check-point-config",template:'
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),he=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allAzureIotHubCredentialsTypes=W,n.azureIotHubCredentialsTypeTranslationsMap=J,n}return y(r,e),r.prototype.configForm=function(){return this.azureIotHubConfigForm},r.prototype.onConfigurationSet=function(e){this.azureIotHubConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[i.Validators.required]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.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,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;return"sas"===t&&(e.credentials={type:t,sasKey:e.credentials.sasKey,caCert:e.credentials.caCert,caCertFileName:e.credentials.caCertFileName}),e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.azureIotHubConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("sasKey").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"sas":t.get("sasKey").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.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})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-azure-iot-hub-config",template:'
\n \n tb.rulenode.topic\n \n \n {{ \'tb.rulenode.topic-required\' | translate }}\n \n \n \n tb.rulenode.hostname\n \n \n {{ \'tb.rulenode.hostname-required\' | translate }}\n \n \n \n tb.rulenode.device-id\n \n \n {{ \'tb.rulenode.device-id-required\' | translate }}\n \n \n \n \n \n tb.rulenode.credentials\n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(azureIotHubConfigForm.get(\'credentials.type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.sas-key\n \n \n {{ \'tb.rulenode.sas-key-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.deviceProfile},r.prototype.onConfigurationSet=function(e){this.deviceProfile=this.fb.group({persistAlarmRulesState:[!!e&&e.persistAlarmRulesState,i.Validators.required],fetchAlarmRulesStateOnStart:[!!e&&e.fetchAlarmRulesStateOnStart,i.Validators.required]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-device-profile-config",template:'
\n \n {{ \'tb.rulenode.persist-alarm-rules\' | translate }}\n \n \n {{ \'tb.rulenode.fetch-alarm-rules\' | translate }}\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ve=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.sendSmsConfigForm},r.prototype.onConfigurationSet=function(e){this.sendSmsConfigForm=this.fb.group({numbersToTemplate:[e?e.numbersToTemplate:null,[i.Validators.required]],smsMessageTemplate:[e?e.smsMessageTemplate:null,[i.Validators.required]],useSystemSmsSettings:[!!e&&e.useSystemSmsSettings,[]],smsProviderConfiguration:[e?e.smsProviderConfiguration:null,[]]})},r.prototype.validatorTriggers=function(){return["useSystemSmsSettings"]},r.prototype.updateValidators=function(e){this.sendSmsConfigForm.get("useSystemSmsSettings").value?this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([]):this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([i.Validators.required]),this.sendSmsConfigForm.get("smsProviderConfiguration").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-send-sms-config",template:'
\n \n tb.rulenode.numbers-to-template\n \n \n {{ \'tb.rulenode.numbers-to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.sms-message-template\n \n \n {{ \'tb.rulenode.sms-message-template-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.use-system-sms-settings\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Fe=function(){function e(){}return e=b([t.NgModule({declarations:[x,T,q,S,I,k,N,V,E,A,L,X,ee,te,re,se,me,ue,de,pe,ce,fe,ge,ye,be,he,Ce,ve],imports:[r.CommonModule,a.SharedModule,l.HomeComponentsModule,le],exports:[x,T,q,S,I,k,N,V,E,A,L,X,ee,te,re,se,me,ue,de,pe,ce,fe,ge,ye,be,he,Ce,ve]})],e)}(),xe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.checkMessageConfigForm},r.prototype.onConfigurationSet=function(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]})},r.prototype.validateConfig=function(){var e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0},r.prototype.removeMessageName=function(e){var t=this.checkMessageConfigForm.get("messageNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))},r.prototype.removeMetadataName=function(e){var t=this.checkMessageConfigForm.get("metadataNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))},r.prototype.addMessageName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("messageNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("messageNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.prototype.addMetadataName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("metadataNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("metadataNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-message-config",template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
tb.rulenode.check-all-keys-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.entitySearchDirection=Object.keys(a.EntitySearchDirection),n.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.checkRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],relationType:[e?e.relationType:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["checkForSingleEntity"]},r.prototype.updateValidators=function(e){var t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-relation-config",template:'
\n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.check-relation-hint
\n \n relation.direction\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }}\n \n \n \n
\n \n \n \n \n
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),qe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=B,n}return y(r,e),r.prototype.configForm=function(){return this.geoFilterConfigForm},r.prototype.onConfigurationSet=function(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType: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,[]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterType").setValidators([]):this.geoFilterConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoFilterConfigForm.get("centerLatitude").setValidators([]),this.geoFilterConfigForm.get("centerLongitude").setValidators([]),this.geoFilterConfigForm.get("range").setValidators([]),this.geoFilterConfigForm.get("rangeUnit").setValidators([])):(this.geoFilterConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoFilterConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoFilterConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),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})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.messageTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e?e.messageTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-message-type-config",template:'
\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ie=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allowedEntityTypes=[a.EntityType.DEVICE,a.EntityType.ASSET,a.EntityType.ENTITY_VIEW,a.EntityType.TENANT,a.EntityType.CUSTOMER,a.EntityType.USER,a.EntityType.DASHBOARD,a.EntityType.RULE_CHAIN,a.EntityType.RULE_NODE],n}return y(r,e),r.prototype.configForm=function(){return this.originatorTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-originator-type-config",template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ke=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ne=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.switchConfigForm},r.prototype.onConfigurationSet=function(e){this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.switchConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ve=function(e){function r(t,r,n){var o,l,s=e.call(this,t)||this;s.store=t,s.translate=r,s.fb=n,s.alarmStatusTranslationsMap=a.alarmStatusTranslations,s.alarmStatusList=[],s.searchText="",s.displayStatusFn=s.displayStatus.bind(s);try{for(var m=C(Object.keys(a.AlarmStatus)),u=m.next();!u.done;u=m.next()){var d=u.value;s.alarmStatusList.push(a.AlarmStatus[d])}}catch(e){o={error:e}}finally{try{u&&!u.done&&(l=m.return)&&l.call(m)}finally{if(o)throw o.error}}return s.statusFormControl=new i.FormControl(""),s.filteredAlarmStatus=s.statusFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return s.fetchAlarmStatus(e)})),f.share()),s}return y(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.alarmStatusConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.statusFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.alarmStatusConfigForm=this.fb.group({alarmStatusList:[e?e.alarmStatusList:null,[i.Validators.required]]})},r.prototype.displayStatus=function(e){return e?this.translate.instant(a.alarmStatusTranslations.get(e)):void 0},r.prototype.fetchAlarmStatus=function(e){var t=this,r=this.getAlarmStatusList();if(this.searchText=e,this.searchText&&this.searchText.length){var n=this.searchText.toUpperCase();return c.of(r.filter((function(e){return t.translate.instant(a.alarmStatusTranslations.get(a.AlarmStatus[e])).toUpperCase().includes(n)})))}return c.of(r)},r.prototype.alarmStatusSelected=function(e){this.addAlarmStatus(e.option.value),this.clear("")},r.prototype.removeAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))}},r.prototype.addAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))},r.prototype.getAlarmStatusList=function(){var e=this;return this.alarmStatusList.filter((function(t){return-1===e.alarmStatusConfigForm.get("alarmStatusList").value.indexOf(t)}))},r.prototype.onAlarmStatusInputFocus=function(){this.statusFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.alarmStatusInput.nativeElement.value=e,this.statusFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.alarmStatusInput.nativeElement.blur(),t.alarmStatusInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("alarmStatusInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"alarmStatusInput",void 0),r=b([t.Component({selector:"tb-filter-node-check-alarm-status-config",template:'
\n \n tb.rulenode.alarm-status-filter\n \n \n \n {{alarmStatusTranslationsMap.get(alarmStatus) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-alarm-status-matching\n
\n
\n
\n
\n
\n \n
\n\n\n\n'}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ee=function(){function e(){}return e=b([t.NgModule({declarations:[xe,Te,qe,Se,Ie,ke,Ne,Ve],imports:[r.CommonModule,a.SharedModule,le],exports:[xe,Te,qe,Se,Ie,ke,Ne,Ve]})],e)}(),Ae=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.customerAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Le=function(e){function r(t,r,n){var a,o,l=e.call(this,t)||this;l.store=t,l.translate=r,l.fb=n,l.entityDetailsTranslationsMap=G,l.entityDetailsList=[],l.searchText="",l.displayDetailsFn=l.displayDetails.bind(l);try{for(var s=C(Object.keys(K)),m=s.next();!m.done;m=s.next()){var u=m.value;l.entityDetailsList.push(K[u])}}catch(e){a={error:e}}finally{try{m&&!m.done&&(o=s.return)&&o.call(s)}finally{if(a)throw a.error}}return l.detailsFormControl=new i.FormControl(""),l.filteredEntityDetails=l.detailsFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return l.fetchEntityDetails(e)})),f.share()),l}return y(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.entityDetailsConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.detailsFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[i.Validators.required]],addToMetadata:[!!e&&e.addToMetadata,[]]})},r.prototype.displayDetails=function(e){return e?this.translate.instant(G.get(e)):void 0},r.prototype.fetchEntityDetails=function(e){var t=this;if(this.searchText=e,this.searchText&&this.searchText.length){var r=this.searchText.toUpperCase();return c.of(this.entityDetailsList.filter((function(e){return t.translate.instant(G.get(K[e])).toUpperCase().includes(r)})))}return c.of(this.entityDetailsList)},r.prototype.detailsFieldSelected=function(e){this.addDetailsField(e.option.value),this.clear("")},r.prototype.removeDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}},r.prototype.addDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))},r.prototype.onEntityDetailsInputFocus=function(){this.detailsFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.detailsInput.nativeElement.blur(),t.detailsInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("detailsInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"detailsInput",void 0),r=b([t.Component({selector:"tb-enrichment-node-entity-details-config",template:'
\n \n tb.rulenode.entity-details\n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}"]}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.deviceAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[i.Validators.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.deviceAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.deviceAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.deviceAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.deviceAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-device-attributes-config",template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.originatorAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.originatorAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.originatorAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.originatorAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.originatorAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-attributes-config",template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.originatorFieldsConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),we=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n.fetchMode=U,n.fetchModes=Object.keys(U),n.samplingOrders=Object.keys(j),n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=D,n}return y(r,e),r.prototype.configForm=function(){return this.getTelemetryFromDatabaseConfigForm},r.prototype.onConfigurationSet=function(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],fetchMode:[e?e.fetchMode:null,[i.Validators.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchMode","useMetadataIntervalPatterns"]},r.prototype.updateValidators=function(e){var t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,r=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===U.ALL?(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([i.Validators.required,i.Validators.min(2),i.Validators.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),r?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([i.Validators.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})},r.prototype.removeKey=function(e,t){var r=this.getTelemetryFromDatabaseConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.getTelemetryFromDatabaseConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\n \n \n {{ \'tb.rulenode.start-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\n \n \n {{ \'tb.rulenode.end-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.relatedAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e?e.relationsQuery:null,[i.Validators.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),De=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.tenantAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ke=function(){function e(){}return e=b([t.NgModule({declarations:[Ae,Le,Me,Pe,Re,we,Oe,De],imports:[r.CommonModule,a.SharedModule,le],exports:[Ae,Le,Me,Pe,Re,we,Oe,De]})],e)}(),Be=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.originatorSource=v,n.originatorSources=Object.keys(v),n.originatorSourceTranslationMap=P,n}return y(r,e),r.prototype.configForm=function(){return this.changeOriginatorConfigForm},r.prototype.onConfigurationSet=function(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[i.Validators.required]],relationsQuery:[e?e.relationsQuery:null,[]]})},r.prototype.validatorTriggers=function(){return["originatorSource"]},r.prototype.updateValidators=function(e){var t=this.changeOriginatorConfigForm.get("originatorSource").value;t&&t===v.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([i.Validators.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-transformation-node-change-originator-config",template:'
\n \n tb.rulenode.originator-source\n \n \n {{ originatorSourceTranslationMap.get(source) | translate }}\n \n \n \n
\n \n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ue=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-transformation-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),je=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.toEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[i.Validators.required]],toTemplate:[e?e.toTemplate:null,[i.Validators.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[i.Validators.required]],bodyTemplate:[e?e.bodyTemplate:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-transformation-node-to-email-config",template:'
\n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n \n \n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.cc-template\n \n \n \n \n tb.rulenode.bcc-template\n \n \n \n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),He=function(){function e(){}return e=b([t.NgModule({declarations:[Be,Ue,je],imports:[r.CommonModule,a.SharedModule,le],exports:[Be,Ue,je]})],e)}(),Ge=function(){function e(e){!function(e){e.setTranslation("en_US",{tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","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 name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","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.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","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","client-attributes-hint":"Client attributes, use ${metaKeyName} to substitute variables from metadata","shared-attributes":"Shared attributes","shared-attributes-hint":"Shared attributes, use ${metaKeyName} to substitute variables from metadata","server-attributes":"Server attributes","server-attributes-hint":"Server attributes, use ${metaKeyName} to substitute variables from metadata","notify-device":"Notify Device","notify-device-hint":"If the message arrives from the device, we will push it back to the device by default.","latest-timeseries":"Latest timeseries","latest-timeseries-hint":"Latest timeseries, use ${metaKeyName} to substitute variables from metadata","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","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","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","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","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required","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",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","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","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","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",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata",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.",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","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","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","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","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 ${metaKeyName} in name/value fields to substitute variables from metadata","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","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-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":"CA certificate file *","private-key":"Private key file *",cert:"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-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys 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-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","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-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","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 ${metaKeyName} to substitute variables from metadata","sms-message-template":"SMS message Template","sms-message-template-required":"SMS message Template is required","sms-message-template-hint":"SMS message template, use ${metaKeyName} to substitute variables from metadata","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.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","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","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details 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":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","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:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, 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 Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size","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.","persist-alarm-rules":"Persist state of alarm rules","fetch-alarm-rules":"Fetch state of alarm rules"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.ctorParameters=function(){return[{type:n.TranslateService}]},e=b([t.NgModule({declarations:[F],imports:[r.CommonModule,a.SharedModule],exports:[Fe,Ee,Ke,He,F]}),h("design:paramtypes",[n.TranslateService])],e)}();e.RuleNodeCoreConfigModule=Ge,e.ɵa=F,e.ɵb=Fe,e.ɵba=be,e.ɵbb=he,e.ɵbc=Ce,e.ɵbd=ve,e.ɵbe=le,e.ɵbf=ne,e.ɵbg=ae,e.ɵbh=oe,e.ɵbi=ie,e.ɵbj=Ee,e.ɵbk=xe,e.ɵbl=Te,e.ɵbm=qe,e.ɵbn=Se,e.ɵbo=Ie,e.ɵbp=ke,e.ɵbq=Ne,e.ɵbr=Ve,e.ɵbs=Ke,e.ɵbt=Ae,e.ɵbu=Le,e.ɵbv=Me,e.ɵbw=Pe,e.ɵbx=Re,e.ɵby=we,e.ɵbz=Oe,e.ɵc=x,e.ɵca=De,e.ɵcb=He,e.ɵcc=Be,e.ɵcd=Ue,e.ɵce=je,e.ɵd=T,e.ɵe=q,e.ɵf=S,e.ɵg=I,e.ɵh=k,e.ɵi=N,e.ɵj=V,e.ɵk=E,e.ɵl=A,e.ɵm=L,e.ɵn=X,e.ɵo=ee,e.ɵp=te,e.ɵq=re,e.ɵr=se,e.ɵs=me,e.ɵt=ue,e.ɵu=de,e.ɵv=pe,e.ɵw=ce,e.ɵx=fe,e.ɵy=ge,e.ɵz=ye,Object.defineProperty(e,"__esModule",{value:!0})})); //# sourceMappingURL=rulenode-core-config.umd.min.js.map \ No newline at end of file diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java index ae6bd7ebbd..39c16247fa 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java @@ -23,17 +23,13 @@ import org.junit.runner.RunWith; import org.mockito.AdditionalAnswers; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; import org.mockito.runners.MockitoJUnitRunner; -import org.mockito.stubbing.Answer; -import org.springframework.util.StringUtils; import org.thingsboard.rule.engine.api.RuleEngineAlarmService; import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.device.profile.AlarmCondition; import org.thingsboard.server.common.data.device.profile.AlarmRule; @@ -52,11 +48,10 @@ import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import java.util.Collections; -import java.util.LinkedHashMap; +import java.util.TreeMap; import java.util.UUID; import static org.mockito.Mockito.verify; @@ -140,7 +135,7 @@ public class TbDeviceProfileNodeTest { DeviceProfileAlarm dpa = new DeviceProfileAlarm(); dpa.setId("highTemperatureAlarmID"); dpa.setAlarmType("highTemperatureAlarm"); - dpa.setCreateRules(new LinkedHashMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); + dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); KeyFilter lowTempFilter = new KeyFilter(); lowTempFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); diff --git a/tools/pom.xml b/tools/pom.xml index 8d596c8493..b3fce1373f 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT thingsboard tools @@ -54,7 +54,6 @@ org.apache.cassandra cassandra-all - 3.11.6 com.datastax.oss diff --git a/transport/coap/pom.xml b/transport/coap/pom.xml index 1b7abf06db..94cb8e88a9 100644 --- a/transport/coap/pom.xml +++ b/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index 3a1d0e7414..2def6330b3 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -160,8 +160,6 @@ queue: max_requests_timeout: "${REMOTE_JS_MAX_REQUEST_TIMEOUT:10000}" # JS response poll interval response_poll_interval: "${REMOTE_JS_RESPONSE_POLL_INTERVAL_MS:25}" - # JS response auto commit interval - response_auto_commit_interval: "${REMOTE_JS_RESPONSE_AUTO_COMMIT_INTERVAL_MS:100}" rule-engine: topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb_rule_engine}" poll-interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}" diff --git a/transport/http/pom.xml b/transport/http/pom.xml index f5ae869c75..59cfef95f0 100644 --- a/transport/http/pom.xml +++ b/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index ef152bb47d..0004400eda 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -153,8 +153,6 @@ queue: max_requests_timeout: "${REMOTE_JS_MAX_REQUEST_TIMEOUT:10000}" # JS response poll interval response_poll_interval: "${REMOTE_JS_RESPONSE_POLL_INTERVAL_MS:25}" - # JS response auto commit interval - response_auto_commit_interval: "${REMOTE_JS_RESPONSE_AUTO_COMMIT_INTERVAL_MS:100}" rule-engine: topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb_rule_engine}" poll-interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}" diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml index a1a45f54b8..b2ba041e0b 100644 --- a/transport/mqtt/pom.xml +++ b/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index fc8d970638..210731cde2 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -182,8 +182,6 @@ queue: max_requests_timeout: "${REMOTE_JS_MAX_REQUEST_TIMEOUT:10000}" # JS response poll interval response_poll_interval: "${REMOTE_JS_RESPONSE_POLL_INTERVAL_MS:25}" - # JS response auto commit interval - response_auto_commit_interval: "${REMOTE_JS_RESPONSE_AUTO_COMMIT_INTERVAL_MS:100}" rule-engine: topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb_rule_engine}" poll-interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}" diff --git a/transport/pom.xml b/transport/pom.xml index 429c953a46..d477fc7de2 100644 --- a/transport/pom.xml +++ b/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT thingsboard transport diff --git a/ui-ngx/angular.json b/ui-ngx/angular.json index 3cad30304a..897b7b86ec 100644 --- a/ui-ngx/angular.json +++ b/ui-ngx/angular.json @@ -59,6 +59,11 @@ "glob": "marker-shadow.png", "input": "node_modules/leaflet/dist/images/", "output": "/" + }, + { + "glob": "**/*", + "input": "node_modules/material-design-icons/iconfont/", + "output": "assets/fonts" } ], "styles": [ diff --git a/ui-ngx/package.json b/ui-ngx/package.json index 8e3ede2934..bf05109bb6 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -1,6 +1,6 @@ { "name": "thingsboard", - "version": "3.2.0", + "version": "3.2.1", "scripts": { "ng": "ng", "start": "node --max_old_space_size=8048 ./node_modules/@angular/cli/bin/ng serve --host 0.0.0.0 --open", diff --git a/ui-ngx/pom.xml b/ui-ngx/pom.xml index 14b9317e4a..0420401dbe 100644 --- a/ui-ngx/pom.xml +++ b/ui-ngx/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.2.0-SNAPSHOT + 3.2.1-SNAPSHOT thingsboard org.thingsboard diff --git a/ui-ngx/src/app/app.component.html b/ui-ngx/src/app/app.component.html index 6521e99533..7330d0c1c1 100644 --- a/ui-ngx/src/app/app.component.html +++ b/ui-ngx/src/app/app.component.html @@ -17,4 +17,4 @@ --> - + diff --git a/ui-ngx/src/app/app.component.ts b/ui-ngx/src/app/app.component.ts index f7d290c5ca..ac3512694e 100644 --- a/ui-ngx/src/app/app.component.ts +++ b/ui-ngx/src/app/app.component.ts @@ -62,7 +62,7 @@ export class AppComponent implements OnInit { this.matIconRegistry.addSvgIconLiteral( 'alpha-e-circle-outline', this.domSanitizer.bypassSecurityTrustHtml( - '' ) @@ -124,5 +124,11 @@ export class AppComponent implements OnInit { ngOnInit() { } -} + onActivateComponent($event: any) { + const loadingElement = $('div#tb-loading-spinner'); + if (loadingElement.length) { + loadingElement.remove(); + } + } +} diff --git a/ui-ngx/src/app/core/api/widget-api.models.ts b/ui-ngx/src/app/core/api/widget-api.models.ts index e4b9deae89..d7dfdc29b4 100644 --- a/ui-ngx/src/app/core/api/widget-api.models.ts +++ b/ui-ngx/src/app/core/api/widget-api.models.ts @@ -198,7 +198,7 @@ export class WidgetSubscriptionContext { export type SubscriptionMessageSeverity = 'info' | 'warn' | 'error' | 'success'; export interface SubscriptionMessage { - severity: SubscriptionMessageSeverity, + severity: SubscriptionMessageSeverity; message: string; } diff --git a/ui-ngx/src/app/core/auth/auth.service.ts b/ui-ngx/src/app/core/auth/auth.service.ts index 1f8788d821..3d4a203252 100644 --- a/ui-ngx/src/app/core/auth/auth.service.ts +++ b/ui-ngx/src/app/core/auth/auth.service.ts @@ -282,8 +282,7 @@ export class AuthService { if (publicId) { return this.publicLogin(publicId).pipe( mergeMap((response) => { - this.updateAndValidateToken(response.token, 'jwt_token', false); - this.updateAndValidateToken(response.refreshToken, 'refresh_token', false); + this.updateAndValidateTokens(response.token, response.refreshToken, false); return this.procceedJwtTokenValidate(); }), catchError((err) => { @@ -317,8 +316,7 @@ export class AuthService { }; return this.http.post('/api/auth/login', loginRequest, defaultHttpOptions()).pipe( mergeMap((loginResponse: LoginResponse) => { - this.updateAndValidateToken(loginResponse.token, 'jwt_token', false); - this.updateAndValidateToken(loginResponse.refreshToken, 'refresh_token', false); + this.updateAndValidateTokens(loginResponse.token, loginResponse.refreshToken, false); return this.procceedJwtTokenValidate(); } ) @@ -439,7 +437,7 @@ export class AuthService { })); } - public refreshJwtToken(): Observable { + public refreshJwtToken(loadUserElseStoreJwtToken = true): Observable { let response: Observable = this.refreshTokenSubject; if (this.refreshTokenSubject === null) { this.refreshTokenSubject = new ReplaySubject(1); @@ -456,7 +454,11 @@ export class AuthService { }; const refreshObservable = this.http.post('/api/auth/token', refreshTokenRequest, defaultHttpOptions()); refreshObservable.subscribe((loginResponse: LoginResponse) => { - this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, false); + if (loadUserElseStoreJwtToken) { + this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, false); + } else { + this.updateAndValidateTokens(loginResponse.token, loginResponse.refreshToken, true); + } this.refreshTokenSubject.next(loginResponse); this.refreshTokenSubject.complete(); this.refreshTokenSubject = null; @@ -474,7 +476,7 @@ export class AuthService { const subject = new ReplaySubject(); if (!AuthService.isTokenValid('jwt_token')) { if (doRefresh) { - this.refreshJwtToken().subscribe( + this.refreshJwtToken(!doRefresh).subscribe( () => { subject.next(); subject.complete(); @@ -505,8 +507,7 @@ export class AuthService { this.notifyUnauthenticated(); } } else { - this.updateAndValidateToken(jwtToken, 'jwt_token', true); - this.updateAndValidateToken(refreshToken, 'refresh_token', true); + this.updateAndValidateTokens(jwtToken, refreshToken, true); if (notify) { this.notifyUserLoaded(false); this.loadUser(false).subscribe( @@ -525,6 +526,11 @@ export class AuthService { } } + private updateAndValidateTokens(jwtToken, refreshToken, notify: boolean) { + this.updateAndValidateToken(jwtToken, 'jwt_token', notify); + this.updateAndValidateToken(refreshToken, 'refresh_token', notify); + } + public parsePublicId(): string { const token = AuthService.getJwtToken(); if (token) { diff --git a/ui-ngx/src/app/core/http/admin.service.ts b/ui-ngx/src/app/core/http/admin.service.ts index f575f56906..a228234c3d 100644 --- a/ui-ngx/src/app/core/http/admin.service.ts +++ b/ui-ngx/src/app/core/http/admin.service.ts @@ -18,7 +18,13 @@ import { Injectable } from '@angular/core'; import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { Observable } from 'rxjs'; import { HttpClient } from '@angular/common/http'; -import { AdminSettings, MailServerSettings, SecuritySettings, UpdateMessage } from '@shared/models/settings.models'; +import { + AdminSettings, + MailServerSettings, + SecuritySettings, + TestSmsRequest, + UpdateMessage +} from '@shared/models/settings.models'; @Injectable({ providedIn: 'root' @@ -43,6 +49,11 @@ export class AdminService { return this.http.post('/api/admin/settings/testMail', adminSettings, defaultHttpOptionsFromConfig(config)); } + public sendTestSms(testSmsRequest: TestSmsRequest, + config?: RequestConfig): Observable { + return this.http.post('/api/admin/settings/testSms', testSmsRequest, defaultHttpOptionsFromConfig(config)); + } + public getSecuritySettings(config?: RequestConfig): Observable { return this.http.get(`/api/admin/securitySettings`, defaultHttpOptionsFromConfig(config)); } diff --git a/ui-ngx/src/app/core/http/dashboard.service.ts b/ui-ngx/src/app/core/http/dashboard.service.ts index 42ba3834c9..61435747b3 100644 --- a/ui-ngx/src/app/core/http/dashboard.service.ts +++ b/ui-ngx/src/app/core/http/dashboard.service.ts @@ -130,7 +130,7 @@ export class DashboardService { const publicCustomerId = publicCustomers[0].customerId.id; let url = this.window.location.protocol + '//' + this.window.location.hostname; const port = this.window.location.port; - if (port !== '80' && port !== '443') { + if (port && port.length > 0 && port !== '80' && port !== '443') { url += ':' + port; } url += `/dashboard/${dashboard.id.id}?publicId=${publicCustomerId}`; diff --git a/ui-ngx/src/app/core/http/device-profile.service.ts b/ui-ngx/src/app/core/http/device-profile.service.ts index c535878fd2..590bdaa20e 100644 --- a/ui-ngx/src/app/core/http/device-profile.service.ts +++ b/ui-ngx/src/app/core/http/device-profile.service.ts @@ -69,4 +69,20 @@ export class DeviceProfileService { return this.http.get>(url, defaultHttpOptionsFromConfig(config)); } + public getDeviceProfileDevicesAttributesKeys(deviceProfileId?: string, config?: RequestConfig): Observable> { + let url = `/api/deviceProfile/devices/keys/attributes`; + if (isDefinedAndNotNull(deviceProfileId)) { + url += `?deviceProfileId=${deviceProfileId}`; + } + return this.http.get>(url, defaultHttpOptionsFromConfig(config)); + } + + public getDeviceProfileDevicesTimeseriesKeys(deviceProfileId?: string, config?: RequestConfig): Observable> { + let url = `/api/deviceProfile/devices/keys/timeseries`; + if (isDefinedAndNotNull(deviceProfileId)) { + url += `?deviceProfileId=${deviceProfileId}`; + } + return this.http.get>(url, defaultHttpOptionsFromConfig(config)); + } + } diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index 9263838ffa..6a1f0191c4 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -869,7 +869,7 @@ export class EntityService { return findEntityObservable.pipe( mergeMap((entity) => { const tasks: Observable[] = []; - const result: Device | Asset = entity as (Device | Asset); + const result: Device & Asset = entity as (Device | Asset); const additionalInfo = result.additionalInfo || {}; if (result.label !== entityData.label || result.type !== entityData.type || @@ -882,6 +882,9 @@ export class EntityService { if (result.id.entityType === EntityType.DEVICE) { result.additionalInfo.gateway = entityData.gateway; } + if (result.id.entityType === EntityType.DEVICE && result.deviceProfileId) { + delete result.deviceProfileId; + } switch (result.id.entityType) { case EntityType.DEVICE: tasks.push(this.deviceService.saveDevice(result, config)); diff --git a/ui-ngx/src/app/core/services/menu.models.ts b/ui-ngx/src/app/core/services/menu.models.ts index 028a0f5380..ad29c17258 100644 --- a/ui-ngx/src/app/core/services/menu.models.ts +++ b/ui-ngx/src/app/core/services/menu.models.ts @@ -23,6 +23,7 @@ export interface MenuSection extends HasUUID{ type: MenuSectionType; path: string; icon: string; + notExact?: boolean; isMdiIcon?: boolean; height?: string; pages?: Array; diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index 4e98f375c2..6184050142 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -108,7 +108,7 @@ export class MenuService { name: 'admin.system-settings', type: 'toggle', path: '/settings', - height: '160px', + height: '200px', icon: 'settings', pages: [ { @@ -125,6 +125,13 @@ export class MenuService { path: '/settings/outgoing-mail', icon: 'mail' }, + { + id: guid(), + name: 'admin.sms-provider', + type: 'link', + path: '/settings/sms-provider', + icon: 'sms' + }, { id: guid(), name: 'admin.security-settings', @@ -187,6 +194,11 @@ export class MenuService { icon: 'mail', path: '/settings/outgoing-mail' }, + { + name: 'admin.sms-provider', + icon: 'sms', + path: '/settings/sms-provider' + }, { name: 'admin.security-settings', icon: 'security', @@ -276,6 +288,14 @@ export class MenuService { type: 'link', path: '/auditLogs', icon: 'track_changes' + }, + { + id: guid(), + name: 'api-usage.api-usage', + type: 'link', + path: '/usage', + icon: 'insert_chart', + notExact: true } ); return sections; @@ -362,6 +382,11 @@ export class MenuService { name: 'audit-log.audit-logs', icon: 'track_changes', path: '/auditLogs' + }, + { + name: 'api-usage.api-usage', + icon: 'insert_chart', + path: '/usage' } ] } diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts index 3613c0f484..faaee28a80 100644 --- a/ui-ngx/src/app/core/services/utils.service.ts +++ b/ui-ngx/src/app/core/services/utils.service.ts @@ -21,17 +21,18 @@ import { Inject, Injectable, NgZone } from '@angular/core'; import { WINDOW } from '@core/services/window.service'; import { ExceptionData } from '@app/shared/models/error.models'; import { + baseUrl, createLabelFromDatasource, deepClone, deleteNullProperties, guid, isDefined, - isDefinedAndNotNull, + isDefinedAndNotNull, isString, isUndefined } from '@core/utils'; import { WindowMessage } from '@shared/models/window-message.model'; import { TranslateService } from '@ngx-translate/core'; -import { customTranslationsPrefix } from '@app/shared/models/constants'; +import { customTranslationsPrefix, i18nPrefix } from '@app/shared/models/constants'; import { DataKey, Datasource, DatasourceType, KeyInfo } from '@shared/models/widget.models'; import { EntityType } from '@shared/models/entity-type.models'; import { DataKeyType } from '@app/shared/models/telemetry/telemetry.models'; @@ -42,6 +43,8 @@ import jsonSchemaDefaults from 'json-schema-defaults'; import materialIconsCodepoints from '!raw-loader!material-design-icons/iconfont/codepoints'; import { Observable, of, ReplaySubject } from 'rxjs'; +const i18nRegExp = new RegExp(`{${i18nPrefix}:[^{}]+}`, 'g'); + const predefinedFunctions: { [func: string]: string } = { Sin: 'return Math.round(1000*Math.sin(time/5000));', Cos: 'return Math.round(1000*Math.cos(time/5000));', @@ -107,8 +110,8 @@ export class UtilsService { materialIcons: Array = []; constructor(@Inject(WINDOW) private window: Window, - private zone: NgZone, - private translate: TranslateService) { + private zone: NgZone, + private translate: TranslateService) { let frame: Element = null; try { frame = window.frameElement; @@ -221,8 +224,31 @@ export class UtilsService { } public customTranslation(translationValue: string, defaultValue: string): string { + if (translationValue && isString(translationValue)) { + if (translationValue.includes(`{${i18nPrefix}`)) { + const matches = translationValue.match(i18nRegExp); + let result = translationValue; + for (const match of matches) { + const translationId = match.substring(6, match.length - 1); + result = result.replace(match, this.doTranslate(translationId, match)); + } + return result; + } else { + return this.doTranslate(translationValue, defaultValue, customTranslationsPrefix); + } + } else { + return translationValue; + } + } + + private doTranslate(translationValue: string, defaultValue: string, prefix?: string): string { let result: string; - const translationId = customTranslationsPrefix + translationValue; + let translationId; + if (prefix) { + translationId = prefix + translationValue; + } else { + translationId = translationValue; + } const translation = this.translate.instant(translationId); if (translation !== translationId) { result = translation + ''; @@ -384,7 +410,7 @@ export class UtilsService { } public updateQueryParam(name: string, value: string | null) { - const baseUrl = [this.window.location.protocol, '//', this.window.location.host, this.window.location.pathname].join(''); + const baseUrlPart = [baseUrl(), this.window.location.pathname].join(''); const urlQueryString = this.window.location.search; let newParam = ''; let params = ''; @@ -404,7 +430,11 @@ export class UtilsService { } else if (newParam) { params = '?' + newParam; } - this.window.history.replaceState({}, '', baseUrl + params); + this.window.history.replaceState({}, '', baseUrlPart + params); + } + + public baseUrl(): string { + return baseUrl(); } public deepClone(target: T, ignoreFields?: string[]): T { diff --git a/ui-ngx/src/app/core/translate/translate-default-compiler.ts b/ui-ngx/src/app/core/translate/translate-default-compiler.ts index a5bf1bbc17..e6110e593c 100644 --- a/ui-ngx/src/app/core/translate/translate-default-compiler.ts +++ b/ui-ngx/src/app/core/translate/translate-default-compiler.ts @@ -65,6 +65,7 @@ export class TranslateDefaultCompiler extends TranslateMessageFormatCompiler { } catch (e) { console.warn(`Failed to parse source: ${src}`); console.error(e); + return false; } const res = tokens.filter( (value) => typeof value !== 'string' && value.type === 'plural' diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index 4650caca77..862e3a5bfd 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -92,6 +92,10 @@ export function isEmptyStr(value: any): boolean { return value === ''; } +export function isNotEmptyStr(value: any): boolean { + return value !== null && typeof value === 'string' && value.trim().length > 0; +} + export function isFunction(value: any): boolean { return typeof value === 'function'; } @@ -381,6 +385,15 @@ export function padValue(val: any, dec: number): string { return strVal; } +export function baseUrl(): string { + let url = window.location.protocol + '//' + window.location.hostname; + const port = window.location.port; + if (port && port.length > 0 && port !== '80' && port !== '443') { + url += ':' + port; + } + return url; +} + export function sortObjectKeys(obj: T): T { return Object.keys(obj).sort().reduce((acc, key) => { acc[key] = obj[key]; diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html index 653e7f841f..063bd0a63d 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html @@ -90,7 +90,7 @@ matTooltipPosition="above" class="mat-subheading-2 title"> {{widget.titleIcon}} - {{widget.title}} + {{widget.customTranslatedTitle}} , + public utils: UtilsService, private timeService: TimeService, private dialogService: DialogService, private breakpointObserver: BreakpointObserver, diff --git a/ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.html b/ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.html new file mode 100644 index 0000000000..77c3d10c4a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.html @@ -0,0 +1,26 @@ + + diff --git a/ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.ts b/ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.ts new file mode 100644 index 0000000000..1297802fc3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/device/copy-device-credentials.component.ts @@ -0,0 +1,132 @@ +/// +/// Copyright © 2016-2020 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 { ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { EntityId } from '@shared/models/id/entity-id'; +import { DeviceService } from '@core/http/device.service'; +import { DeviceCredentials, DeviceCredentialsType } from '@shared/models/device.models'; +import { isDefinedAndNotNull, isEqual } from '@core/utils'; +import { BehaviorSubject, Subject, Subscription } from 'rxjs'; +import { distinctUntilChanged, filter, mergeMap, tap } from 'rxjs/operators'; +import { EntityType } from '@shared/models/entity-type.models'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { TranslateService } from '@ngx-translate/core'; + +@Component({ + selector: 'tb-copy-device-credentials', + templateUrl: './copy-device-credentials.component.html', + styleUrls: [] +}) +export class CopyDeviceCredentialsComponent implements OnInit, OnDestroy { + + private deviceId$ = new BehaviorSubject(null); + + private tooltipMessage: string; + + public hideButton = true; + + public credential: string; + + public loading = false; + + public buttonLabel: string; + + @Input() + set deviceId(deviceId: EntityId) { + this.deviceId$.next(deviceId); + } + + @Input() disabled: boolean; + + @Input() + credentials$: Subject; + + private credentialsSubscription: Subscription = null; + + constructor(private store: Store, + private translate: TranslateService, + private deviceService: DeviceService, + private cd: ChangeDetectorRef + ) { + this.deviceId$.pipe( + filter(device => isDefinedAndNotNull(device) && device.entityType === EntityType.DEVICE), + distinctUntilChanged((prev, curr) => prev.id === curr.id), + tap(() => this.loading = true), + mergeMap(device => this.deviceService.getDeviceCredentials(device.id)) + ).subscribe(deviceCredentials => { + this.processingValue(deviceCredentials); + this.loading = false; + }); + } + + ngOnInit(): void { + this.credentialsSubscription = this.credentials$.pipe( + filter(credential => isDefinedAndNotNull(credential)), + distinctUntilChanged((prev, curr) => isEqual(prev, curr)) + ).subscribe(deviceCredentials => { + this.processingValue(deviceCredentials); + this.cd.detectChanges(); + }); + } + + ngOnDestroy(): void { + this.deviceId$.unsubscribe(); + if (this.credentialsSubscription !== null) { + this.credentialsSubscription.unsubscribe(); + } + } + + private processingValue(credential: DeviceCredentials): void { + switch (credential.credentialsType) { + case DeviceCredentialsType.ACCESS_TOKEN: + this.hideButton = false; + this.credential = credential.credentialsId; + this.buttonLabel = this.translate.instant('device.copyAccessToken'); + this.tooltipMessage = this.translate.instant('device.accessTokenCopiedMessage'); + break; + case DeviceCredentialsType.MQTT_BASIC: + this.hideButton = false; + this.credential = this.convertObjectToString(JSON.parse(credential.credentialsValue)); + this.buttonLabel = this.translate.instant('device.copy-mqtt-authentication'); + this.tooltipMessage = this.translate.instant('device.mqtt-authentication-copied-message'); + break; + default: + this.hideButton = true; + this.credential = null; + this.buttonLabel = ''; + this.tooltipMessage = ''; + } + } + + private convertObjectToString(obj: object): string { + Object.keys(obj).forEach(k => (!obj[k] && obj[k] !== undefined) && delete obj[k]); + return JSON.stringify(obj).replace(/"([^"]+)":/g, '$1:'); + } + + onCopyCredential() { + this.store.dispatch(new ActionNotificationShow( + { + message: this.tooltipMessage, + type: 'success', + duration: 750, + verticalPosition: 'bottom', + horizontalPosition: 'right' + })); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.ts index 64100eacfc..2555b3d336 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.ts @@ -147,6 +147,9 @@ export class EntityFilterComponent implements ControlValueAccessor, OnInit { entityViewNameFilter: [filter ? filter.entityViewNameFilter : '', []], }); break; + case AliasFilterType.apiUsageState: + this.filterFormGroup = this.fb.group({}); + break; case AliasFilterType.relationsQuery: case AliasFilterType.assetSearchQuery: case AliasFilterType.deviceSearchQuery: @@ -190,7 +193,7 @@ export class EntityFilterComponent implements ControlValueAccessor, OnInit { private filterTypeChanged(type: AliasFilterType) { let resolveMultiple = true; - if (type === AliasFilterType.singleEntity || type === AliasFilterType.stateEntity) { + if (type === AliasFilterType.singleEntity || type === AliasFilterType.stateEntity || type === AliasFilterType.apiUsageState) { resolveMultiple = false; } if (this.resolveMultiple !== resolveMultiple) { diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html b/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html index 524285ea3f..01106137b9 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html @@ -17,28 +17,28 @@ -->
-
+
- + - + - - + {{ (filterPredicateValueFormGroup.get('defaultValue').value ? 'value.true' : 'value.false') | translate }} @@ -46,9 +46,9 @@
filter.default-value
-
+
- + @@ -62,7 +62,7 @@
filter.dynamic-source-type
- + diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-user-info-dialog.component.html b/ui-ngx/src/app/modules/home/components/filter/filter-user-info-dialog.component.html index 587504da45..9be0838fb8 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-user-info-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/filter-user-info-dialog.component.html @@ -27,10 +27,10 @@
- + {{ 'filter.editable' | translate }} -
+
filter.display-label @@ -39,7 +39,7 @@ {{ 'filter.autogenerated-label' | translate }}
- + filter.order-priority diff --git a/ui-ngx/src/app/modules/home/components/filter/filters-edit-panel.component.html b/ui-ngx/src/app/modules/home/components/filter/filters-edit-panel.component.html index 3de6b75bc4..775bfddaaa 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filters-edit-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/filters-edit-panel.component.html @@ -16,8 +16,8 @@ -->
-
-
+
+
{{filter.value.filter}} + + + diff --git a/ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.ts b/ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.ts index 202d37be34..b683c5cb17 100644 --- a/ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; +import { Component, ElementRef, Inject, OnDestroy, OnInit, SkipSelf, ViewChild } from '@angular/core'; import { ErrorStateMatcher } from '@angular/material/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; @@ -32,9 +32,12 @@ import { } from '@shared/models/query/query.models'; import { DialogService } from '@core/services/dialog.service'; import { TranslateService } from '@ngx-translate/core'; -import { EntityField, entityFields } from '@shared/models/entity.models'; -import { Observable } from 'rxjs'; -import { filter, map, startWith } from 'rxjs/operators'; +import { entityFields } from '@shared/models/entity.models'; +import { Observable, of, Subject } from 'rxjs'; +import { filter, map, mergeMap, publishReplay, refCount, startWith, takeUntil } from 'rxjs/operators'; +import { isDefined } from '@core/utils'; +import { EntityId } from '@shared/models/id/entity-id'; +import { DeviceProfileService } from '@core/http/device-profile.service'; export interface KeyFilterDialogData { keyFilter: KeyFilterInfo; @@ -43,6 +46,7 @@ export interface KeyFilterDialogData { allowUserDynamicSource: boolean; readonly: boolean; telemetryKeysOnly: boolean; + entityId?: EntityId; } @Component({ @@ -53,7 +57,13 @@ export interface KeyFilterDialogData { }) export class KeyFilterDialogComponent extends DialogComponent - implements OnInit, ErrorStateMatcher { + implements OnInit, OnDestroy, ErrorStateMatcher { + + @ViewChild('keyNameInput', {static: true}) private keyNameInput: ElementRef; + + private dirty = false; + private entityKeysName: Observable>; + private destroy$ = new Subject(); keyFilterFormGroup: FormGroup; @@ -72,19 +82,18 @@ export class KeyFilterDialogComponent extends submitted = false; - entityFields: { [fieldName: string]: EntityField }; - - entityFieldsList: string[]; + showAutocomplete = false; - readonly entityField = EntityKeyType.ENTITY_FIELD; + filteredKeysName: Observable>; - filteredEntityFields: Observable; + searchText = ''; constructor(protected store: Store, protected router: Router, @Inject(MAT_DIALOG_DATA) public data: KeyFilterDialogData, @SkipSelf() private errorStateMatcher: ErrorStateMatcher, public dialogRef: MatDialogRef, + private deviceProfileService: DeviceProfileService, private dialogs: DialogService, private translate: TranslateService, private fb: FormBuilder) { @@ -104,7 +113,9 @@ export class KeyFilterDialogComponent extends ); if (!this.data.readonly) { - this.keyFilterFormGroup.get('valueType').valueChanges.subscribe((valueType: EntityKeyValueType) => { + this.keyFilterFormGroup.get('valueType').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((valueType: EntityKeyValueType) => { const prevValue: EntityKeyValueType = this.keyFilterFormGroup.value.valueType; const predicates: KeyFilterPredicate[] = this.keyFilterFormGroup.get('predicates').value; if (prevValue && prevValue !== valueType && predicates && predicates.length) { @@ -121,11 +132,26 @@ export class KeyFilterDialogComponent extends } }); + this.keyFilterFormGroup.get('key.type').valueChanges.pipe( + startWith(this.data.keyFilter.key.type), + takeUntil(this.destroy$) + ).subscribe((type: EntityKeyType) => { + if (type === EntityKeyType.ENTITY_FIELD || isDefined(this.data.entityId)) { + this.entityKeysName = null; + this.dirty = false; + this.showAutocomplete = true; + } else { + this.showAutocomplete = false; + } + }); + this.keyFilterFormGroup.get('key.key').valueChanges.pipe( - filter((keyName) => this.keyFilterFormGroup.get('key.type').value === this.entityField && this.entityFields.hasOwnProperty(keyName)) + filter((keyName) => + this.keyFilterFormGroup.get('key.type').value === EntityKeyType.ENTITY_FIELD && entityFields.hasOwnProperty(keyName)), + takeUntil(this.destroy$) ).subscribe((keyName: string) => { const prevValueType: EntityKeyValueType = this.keyFilterFormGroup.value.valueType; - const newValueType = this.entityFields[keyName]?.time ? EntityKeyValueType.DATE_TIME : EntityKeyValueType.STRING; + const newValueType = entityFields[keyName]?.time ? EntityKeyValueType.DATE_TIME : EntityKeyValueType.STRING; if (prevValueType !== newValueType) { this.keyFilterFormGroup.get('valueType').patchValue(newValueType, {emitEvent: false}); } @@ -133,18 +159,20 @@ export class KeyFilterDialogComponent extends } else { this.keyFilterFormGroup.disable({emitEvent: false}); } + } - this.entityFields = entityFields; - this.entityFieldsList = Object.values(entityFields).map(entityField => entityField.keyName).sort(); + ngOnInit() { + this.filteredKeysName = this.keyFilterFormGroup.get('key.key').valueChanges + .pipe( + map(value => value ? value : ''), + mergeMap(name => this.fetchEntityName(name)) + ); } - ngOnInit(): void { - this.filteredEntityFields = this.keyFilterFormGroup.get('key.key').valueChanges.pipe( - startWith(''), - map(value => { - return this.entityFieldsList.filter(option => option.startsWith(value)); - }) - ); + ngOnDestroy() { + super.ngOnDestroy(); + this.destroy$.next(); + this.destroy$.complete(); } isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { @@ -157,6 +185,21 @@ export class KeyFilterDialogComponent extends this.dialogRef.close(null); } + clear() { + this.keyFilterFormGroup.get('key.key').patchValue('', {emitEvent: true}); + setTimeout(() => { + this.keyNameInput.nativeElement.blur(); + this.keyNameInput.nativeElement.focus(); + }, 0); + } + + onFocus() { + if (!this.dirty && this.showAutocomplete) { + this.keyFilterFormGroup.get('key.key').updateValueAndValidity({onlySelf: true, emitEvent: true}); + this.dirty = true; + } + } + save(): void { this.submitted = true; if (this.keyFilterFormGroup.valid) { @@ -164,4 +207,41 @@ export class KeyFilterDialogComponent extends this.dialogRef.close(keyFilter); } } + + private fetchEntityName(searchText?: string): Observable> { + this.searchText = searchText; + return this.getEntityKeys().pipe( + map(keys => searchText ? keys.filter(key => key.toUpperCase().startsWith(searchText.toUpperCase())) : keys) + ); + } + + private getEntityKeys(): Observable> { + if (!this.entityKeysName) { + let keyNameObservable: Observable>; + switch (this.keyFilterFormGroup.get('key.type').value) { + case EntityKeyType.ENTITY_FIELD: + keyNameObservable = of(Object.values(entityFields).map(entityField => entityField.keyName).sort()); + break; + case EntityKeyType.ATTRIBUTE: + keyNameObservable = this.deviceProfileService.getDeviceProfileDevicesAttributesKeys( + this.data.entityId?.id, + {ignoreLoading: true} + ); + break; + case EntityKeyType.TIME_SERIES: + keyNameObservable = this.deviceProfileService.getDeviceProfileDevicesTimeseriesKeys( + this.data.entityId?.id, + {ignoreLoading: true} + ); + break; + default: + keyNameObservable = of([]); + } + this.entityKeysName = keyNameObservable.pipe( + publishReplay(1), + refCount() + ); + } + return this.entityKeysName; + } } diff --git a/ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.html b/ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.html index 26dbaaec4a..cf670d7b27 100644 --- a/ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.html @@ -41,7 +41,7 @@ filter.operation.and
-
+
{{ keyFilterControl.value.key.key }}
{{ entityKeyTypeTranslations.get(keyFilterControl.value.key.type) }}
-
+
@@ -79,14 +80,16 @@
device-profile.create-alarm-rules
- +
device-profile.clear-alarm-rule
-
+ style="padding-bottom: 8px;">
- +
-
+
@@ -30,8 +31,7 @@ device-profile.no-alarm-rules
-
+
{{ 'device-profile.provision-device-key-required' | translate }} @@ -38,6 +46,14 @@ device-profile.provision-device-secret + {{ 'device-profile.provision-device-secret-required' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts index e25c07e005..393aadb80c 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts @@ -33,6 +33,10 @@ import { deviceProvisionTypeTranslationMap } from '@shared/models/device.models'; import { generateSecret, isDefinedAndNotNull } from '@core/utils'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'tb-device-profile-provision-configuration', @@ -73,7 +77,9 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu private propagateChange = (v: any) => { }; - constructor(private fb: FormBuilder) { + constructor(protected store: Store, + private fb: FormBuilder, + private translate: TranslateService) { } ngOnInit(): void { @@ -149,4 +155,15 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu } this.propagateChange(deviceProvisionConfiguration); } + + onProvisionCopied(isKey: boolean) { + this.store.dispatch(new ActionNotificationShow( + { + message: this.translate.instant(isKey ? 'device-profile.provision-key-copied-message' : 'device-profile.provision-secret-copied-message'), + type: 'success', + duration: 1200, + verticalPosition: 'bottom', + horizontalPosition: 'right' + })); + } } diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html index 468e946a74..4cba236d2a 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html @@ -39,7 +39,7 @@
-
+
@@ -116,7 +116,8 @@ + formControlName="alarms" + [deviceProfileId]="deviceProfileId"> diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts index d30c8b47b9..a17d09075c 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts @@ -38,7 +38,8 @@ import { } from '@shared/models/device.models'; import { EntityType } from '@shared/models/entity-type.models'; import { RuleChainId } from '@shared/models/id/rule-chain-id'; -import {ServiceType} from "@shared/models/queue.models"; +import { ServiceType } from '@shared/models/queue.models'; +import { EntityId } from '@shared/models/id/entity-id'; @Component({ selector: 'tb-device-profile', @@ -66,6 +67,8 @@ export class DeviceProfileComponent extends EntityComponent { serviceType = ServiceType.TB_RULE_ENGINE; + deviceProfileId: EntityId; + constructor(protected store: Store, protected translate: TranslateService, @Optional() @Inject('entity') protected entityValue: DeviceProfile, @@ -83,12 +86,13 @@ export class DeviceProfileComponent extends EntityComponent { } buildForm(entity: DeviceProfile): FormGroup { + this.deviceProfileId = entity?.id ? entity.id : null; this.displayProfileConfiguration = entity && entity.type && deviceProfileTypeConfigurationInfoMap.get(entity.type).hasProfileConfiguration; this.displayTransportConfiguration = entity && entity.transportType && deviceTransportTypeConfigurationInfoMap.get(entity.transportType).hasProfileConfiguration; const deviceProvisionConfiguration: DeviceProvisionConfiguration = { - type: entity?.provisionType ? entity?.provisionType : DeviceProvisionType.DISABLED, + type: entity?.provisionType ? entity.provisionType : DeviceProvisionType.DISABLED, provisionDeviceKey: entity?.provisionDeviceKey, provisionDeviceSecret: entity?.profileData?.provisionConfiguration?.provisionDeviceSecret }; @@ -157,12 +161,13 @@ export class DeviceProfileComponent extends EntityComponent { } updateForm(entity: DeviceProfile) { + this.deviceProfileId = entity.id; this.displayProfileConfiguration = entity.type && deviceProfileTypeConfigurationInfoMap.get(entity.type).hasProfileConfiguration; this.displayTransportConfiguration = entity.transportType && deviceTransportTypeConfigurationInfoMap.get(entity.transportType).hasProfileConfiguration; const deviceProvisionConfiguration: DeviceProvisionConfiguration = { - type: entity?.provisionType ? entity?.provisionType : DeviceProvisionType.DISABLED, + type: entity?.provisionType ? entity.provisionType : DeviceProvisionType.DISABLED, provisionDeviceKey: entity?.provisionDeviceKey, provisionDeviceSecret: entity?.profileData?.provisionConfiguration?.provisionDeviceSecret }; @@ -176,10 +181,10 @@ export class DeviceProfileComponent extends EntityComponent { transportConfiguration: entity.profileData?.transportConfiguration, alarms: entity.profileData?.alarms, provisionConfiguration: deviceProvisionConfiguration - }}); - this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null}); - this.entityForm.patchValue({defaultQueueName: entity.defaultQueueName}); - this.entityForm.patchValue({description: entity.description}); + }}, {emitEvent: false}); + this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null}, {emitEvent: false}); + this.entityForm.patchValue({defaultQueueName: entity.defaultQueueName}, {emitEvent: false}); + this.entityForm.patchValue({description: entity.description}, {emitEvent: false}); } prepareFormValue(formValue: any): any { diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html index 5b3f3d8abf..a52826bc4f 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html @@ -16,59 +16,79 @@ --> -
+
+ device-profile.mqtt-device-topic-filters +
+
+ + device-profile.telemetry-topic-filter + + + {{ 'device-profile.telemetry-topic-filter-required' | translate}} + + + {{ 'device-profile.not-valid-single-character' | translate}} + + + {{ 'device-profile.not-valid-multi-character' | translate}} + + + + device-profile.attributes-topic-filter + + + {{ 'device-profile.attributes-topic-filter-required' | translate}} + + + {{ 'device-profile.not-valid-single-character' | translate}} + + + {{ 'device-profile.not-valid-multi-character' | translate}} + + +
+ + {{ 'device-profile.mqtt-device-topic-filters-unique' | translate }} + +
+
+
+
+
+
- device-profile.mqtt-device-topic-filters + device-profile.mqtt-device-payload-type
- device-profile.mqtt-device-payload-type {{mqttTransportPayloadTypeTranslations.get(type) | translate}} - + {{ 'device-profile.mqtt-payload-type-required' | translate }} -
+
- device-profile.telemetry-topic-filter - - - {{ 'device-profile.telemetry-topic-filter-required' | translate}} - - - {{ 'device-profile.not-valid-single-character' | translate}} - - - {{ 'device-profile.not-valid-multi-character' | translate}} + device-profile.telemetry-proto-schema + + + {{ 'device-profile.telemetry-proto-schema-required' | translate}} - device-profile.attributes-topic-filter - - - {{ 'device-profile.attributes-topic-filter-required' | translate}} - - - {{ 'device-profile.not-valid-single-character' | translate}} - - - {{ 'device-profile.not-valid-multi-character' | translate}} + device-profile.attributes-proto-schema + + + {{ 'device-profile.attributes-proto-schema-required' | translate}}
- - {{ 'device-profile.mqtt-device-topic-filters-unique' | translate }} - -
-
-
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index c36d685fec..5d26b9f8c8 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -28,10 +28,11 @@ import { Store } from '@ngrx/store'; import { AppState } from '@app/core/core.state'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { - MqttTransportPayloadType, DeviceProfileTransportConfiguration, DeviceTransportType, - MqttDeviceProfileTransportConfiguration, mqttTransportPayloadTypeTranslationMap + MqttDeviceProfileTransportConfiguration, + MqttTransportPayloadType, + mqttTransportPayloadTypeTranslationMap } from '@shared/models/device.models'; import { isDefinedAndNotNull } from '@core/utils'; @@ -53,6 +54,34 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control mqttDeviceProfileTransportConfigurationFormGroup: FormGroup; + private defaultTelemetrySchema = + 'syntax ="proto3";\n' + + 'package telemetry;\n' + + '\n' + + 'message SensorDataReading {\n' + + '\n' + + ' double temperature = 1;\n' + + ' double humidity = 2;\n' + + ' InnerObject innerObject = 3;\n' + + '\n' + + ' message InnerObject {\n' + + ' string key1 = 1;\n' + + ' bool key2 = 2;\n' + + ' double key3 = 3;\n' + + ' int32 key4 = 4;\n' + + ' string key5 = 5;\n' + + ' }\n' + + '}\n'; + + private defaultAttributesSchema = + 'syntax ="proto3";\n' + + 'package attributes;\n' + + '\n' + + 'message SensorConfiguration {\n' + + ' string firmwareVersion = 1;\n' + + ' string serialNumber = 2;\n' + + '}'; + private requiredValue: boolean; get required(): boolean { @@ -82,11 +111,18 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control ngOnInit() { this.mqttDeviceProfileTransportConfigurationFormGroup = this.fb.group({ - configuration: this.fb.group({ deviceAttributesTopic: [null, [Validators.required, this.validationMQTTTopic()]], deviceTelemetryTopic: [null, [Validators.required, this.validationMQTTTopic()]], - transportPayloadType: [MqttTransportPayloadType.JSON, Validators.required] - }, {validator: this.uniqueDeviceTopicValidator}) + transportPayloadTypeConfiguration: this.fb.group({ + transportPayloadType: [MqttTransportPayloadType.JSON, Validators.required], + deviceTelemetryProtoSchema: [this.defaultTelemetrySchema, Validators.required], + deviceAttributesProtoSchema: [this.defaultAttributesSchema, Validators.required] + }) + }, {validator: this.uniqueDeviceTopicValidator} + ); + this.mqttDeviceProfileTransportConfigurationFormGroup.get('transportPayloadTypeConfiguration.transportPayloadType') + .valueChanges.subscribe(payloadType => { + this.updateTransportPayloadBasedControls(payloadType, true); }); this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { this.updateModel(); @@ -102,21 +138,45 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control } } + get protoPayloadType(): boolean { + const transportPayloadType = this.mqttDeviceProfileTransportConfigurationFormGroup.get('transportPayloadTypeConfiguration.transportPayloadType').value; + return transportPayloadType === MqttTransportPayloadType.PROTOBUF; + } + writeValue(value: MqttDeviceProfileTransportConfiguration | null): void { if (isDefinedAndNotNull(value)) { - this.mqttDeviceProfileTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); + this.mqttDeviceProfileTransportConfigurationFormGroup.patchValue(value, {emitEvent: false}); + this.updateTransportPayloadBasedControls(value.transportPayloadTypeConfiguration?.transportPayloadType); } } private updateModel() { let configuration: DeviceProfileTransportConfiguration = null; if (this.mqttDeviceProfileTransportConfigurationFormGroup.valid) { - configuration = this.mqttDeviceProfileTransportConfigurationFormGroup.getRawValue().configuration; + configuration = this.mqttDeviceProfileTransportConfigurationFormGroup.value; configuration.type = DeviceTransportType.MQTT; } this.propagateChange(configuration); } + private updateTransportPayloadBasedControls(type: MqttTransportPayloadType, forceUpdated = false) { + const transportPayloadTypeForm = this.mqttDeviceProfileTransportConfigurationFormGroup + .get('transportPayloadTypeConfiguration') as FormGroup; + if (forceUpdated) { + transportPayloadTypeForm.patchValue({ + deviceTelemetryProtoSchema: this.defaultTelemetrySchema, + deviceAttributesProtoSchema: this.defaultAttributesSchema + }, {emitEvent: false}); + } + if (type === MqttTransportPayloadType.PROTOBUF && !this.disabled) { + transportPayloadTypeForm.get('deviceTelemetryProtoSchema').enable({emitEvent: false}); + transportPayloadTypeForm.get('deviceAttributesProtoSchema').enable({emitEvent: false}); + } else { + transportPayloadTypeForm.get('deviceTelemetryProtoSchema').disable({emitEvent: false}); + transportPayloadTypeForm.get('deviceAttributesProtoSchema').disable({emitEvent: false}); + } + } + private validationMQTTTopic(): ValidatorFn { return (c: FormControl) => { const newTopic = c.value; diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html index 12258ffd5f..bc31b653b5 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html @@ -40,6 +40,54 @@ {{ 'tenant-profile.maximum-assets-range' | translate}} + + tenant-profile.maximum-customers + + + {{ 'tenant-profile.maximum-customers-required' | translate}} + + + {{ 'tenant-profile.maximum-customers-range' | translate}} + + + + tenant-profile.maximum-users + + + {{ 'tenant-profile.maximum-users-required' | translate}} + + + {{ 'tenant-profile.maximum-users-range' | translate}} + + + + tenant-profile.maximum-dashboards + + + {{ 'tenant-profile.maximum-dashboards-required' | translate}} + + + {{ 'tenant-profile.maximum-dashboards-range' | translate}} + + + + tenant-profile.maximum-rule-chains + + + {{ 'tenant-profile.maximum-rule-chains-required' | translate}} + + + {{ 'tenant-profile.maximum-rule-chains-range' | translate}} + + tenant-profile.max-transport-messages + + tenant-profile.default-storage-ttl-days + + + {{ 'tenant-profile.default-storage-ttl-days-required' | translate}} + + + {{ 'tenant-profile.default-storage-ttl-days-range' | translate}} + + tenant-profile.max-rule-node-executions-per-message + + tenant-profile.max-emails + + + {{ 'tenant-profile.max-emails-required' | translate}} + + + {{ 'tenant-profile.max-emails-range' | translate}} + + + + tenant-profile.max-sms + + + {{ 'tenant-profile.max-sms-required' | translate}} + + + {{ 'tenant-profile.max-sms-range' | translate}} + + tenant-profile.transport-tenant-msg-rate-limit diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts index 1b54126fd5..2c901dc789 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts @@ -55,6 +55,10 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA this.defaultTenantProfileConfigurationFormGroup = this.fb.group({ maxDevices: [null, [Validators.required, Validators.min(0)]], maxAssets: [null, [Validators.required, Validators.min(0)]], + maxCustomers: [null, [Validators.required, Validators.min(0)]], + maxUsers: [null, [Validators.required, Validators.min(0)]], + maxDashboards: [null, [Validators.required, Validators.min(0)]], + maxRuleChains: [null, [Validators.required, Validators.min(0)]], transportTenantMsgRateLimit: [null, []], transportTenantTelemetryMsgRateLimit: [null, []], transportTenantTelemetryDataPointsRateLimit: [null, []], @@ -66,7 +70,10 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA maxREExecutions: [null, [Validators.required, Validators.min(0)]], maxJSExecutions: [null, [Validators.required, Validators.min(0)]], maxDPStorageDays: [null, [Validators.required, Validators.min(0)]], - maxRuleNodeExecutionsPerMessage: [null, [Validators.required, Validators.min(0)]] + maxRuleNodeExecutionsPerMessage: [null, [Validators.required, Validators.min(0)]], + maxEmails: [null, [Validators.required, Validators.min(0)]], + maxSms: [null, [Validators.required, Validators.min(0)]], + defaultStorageTtlDays: [null, [Validators.required, Validators.min(0)]] }); this.defaultTenantProfileConfigurationFormGroup.valueChanges.subscribe(() => { this.updateModel(); diff --git a/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.html b/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.html new file mode 100644 index 0000000000..60d1ff9a1e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.html @@ -0,0 +1,40 @@ + + + + admin.aws-access-key-id + + + {{ 'admin.aws-access-key-id-required' | translate }} + + + + admin.aws-secret-access-key + + + {{ 'admin.aws-secret-access-key-required' | translate }} + + + + admin.aws-region + + + {{ 'admin.aws-region-required' | translate }} + + + diff --git a/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.ts b/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.ts new file mode 100644 index 0000000000..a223df4bb1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.ts @@ -0,0 +1,104 @@ +/// +/// Copyright © 2016-2020 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, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@app/core/core.state'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { isDefinedAndNotNull } from '@core/utils'; +import { + AwsSnsSmsProviderConfiguration, + SmsProviderConfiguration, + SmsProviderType +} from '@shared/models/settings.models'; + +@Component({ + selector: 'tb-aws-sns-provider-configuration', + templateUrl: './aws-sns-provider-configuration.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => AwsSnsProviderConfigurationComponent), + multi: true + }] +}) +export class AwsSnsProviderConfigurationComponent implements ControlValueAccessor, OnInit { + + awsSnsProviderConfigurationFormGroup: FormGroup; + + private requiredValue: boolean; + + get required(): boolean { + return this.requiredValue; + } + + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + private fb: FormBuilder) { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.awsSnsProviderConfigurationFormGroup = this.fb.group({ + accessKeyId: [null, [Validators.required]], + secretAccessKey: [null, [Validators.required]], + region: [null, [Validators.required]] + }); + this.awsSnsProviderConfigurationFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.awsSnsProviderConfigurationFormGroup.disable({emitEvent: false}); + } else { + this.awsSnsProviderConfigurationFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: AwsSnsSmsProviderConfiguration | null): void { + if (isDefinedAndNotNull(value)) { + this.awsSnsProviderConfigurationFormGroup.patchValue(value, {emitEvent: false}); + } + } + + private updateModel() { + let configuration: AwsSnsSmsProviderConfiguration = null; + if (this.awsSnsProviderConfigurationFormGroup.valid) { + configuration = this.awsSnsProviderConfigurationFormGroup.value; + (configuration as SmsProviderConfiguration).type = SmsProviderType.AWS_SNS; + } + this.propagateChange(configuration); + } +} diff --git a/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.html b/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.html new file mode 100644 index 0000000000..e852b82314 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.html @@ -0,0 +1,44 @@ + +
+ + admin.sms-provider-type + + + {{smsProviderTypeTranslations.get(type) | translate}} + + + + {{ 'admin.sms-provider-type-required' | translate }} + + +
+ + + + + + + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.ts b/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.ts new file mode 100644 index 0000000000..feea8b9939 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.ts @@ -0,0 +1,123 @@ +/// +/// Copyright © 2016-2020 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, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@app/core/core.state'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { + DeviceProfileTransportConfiguration, + DeviceTransportType, + deviceTransportTypeTranslationMap +} from '@shared/models/device.models'; +import { deepClone } from '@core/utils'; +import { + createSmsProviderConfiguration, + SmsProviderConfiguration, smsProviderConfigurationValidator, + SmsProviderType, + smsProviderTypeTranslationMap +} from '@shared/models/settings.models'; + +@Component({ + selector: 'tb-sms-provider-configuration', + templateUrl: './sms-provider-configuration.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => SmsProviderConfigurationComponent), + multi: true + }] +}) +export class SmsProviderConfigurationComponent implements ControlValueAccessor, OnInit { + + smsProviderType = SmsProviderType; + smsProviderTypes = Object.keys(SmsProviderType); + smsProviderTypeTranslations = smsProviderTypeTranslationMap; + + smsProviderConfigurationFormGroup: FormGroup; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + private fb: FormBuilder) { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.smsProviderConfigurationFormGroup = this.fb.group({ + type: [null, Validators.required], + configuration: [null, smsProviderConfigurationValidator(true)] + }); + this.smsProviderConfigurationFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + this.smsProviderConfigurationFormGroup.get('type').valueChanges.subscribe(() => { + this.smsProviderTypeChanged(); + }); + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.smsProviderConfigurationFormGroup.disable({emitEvent: false}); + } else { + this.smsProviderConfigurationFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: SmsProviderConfiguration | null): void { + const configuration = deepClone(value); + const type = configuration?.type; + if (configuration) { + delete configuration.type; + } + this.smsProviderConfigurationFormGroup.patchValue({type}, {emitEvent: false}); + this.smsProviderConfigurationFormGroup.patchValue({configuration}, {emitEvent: false}); + } + + private smsProviderTypeChanged() { + const type: SmsProviderType = this.smsProviderConfigurationFormGroup.get('type').value; + this.smsProviderConfigurationFormGroup.patchValue({configuration: createSmsProviderConfiguration(type)}, {emitEvent: false}); + } + + private updateModel() { + let configuration: SmsProviderConfiguration = null; + if (this.smsProviderConfigurationFormGroup.valid) { + configuration = this.smsProviderConfigurationFormGroup.getRawValue().configuration; + configuration.type = this.smsProviderConfigurationFormGroup.getRawValue().type; + } + this.propagateChange(configuration); + } +} diff --git a/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.html b/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.html new file mode 100644 index 0000000000..449140a18e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.html @@ -0,0 +1,44 @@ + +
+ + admin.number-from + + + {{ 'admin.number-from-required' | translate }} + + + {{ 'admin.phone-number-pattern' | translate }} + + + + + admin.twilio-account-sid + + + {{ 'admin.twilio-account-sid-required' | translate }} + + + + admin.twilio-account-token + + + {{ 'admin.twilio-account-token-required' | translate }} + + +
diff --git a/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.ts b/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.ts new file mode 100644 index 0000000000..5577a16a00 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.ts @@ -0,0 +1,106 @@ +/// +/// Copyright © 2016-2020 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, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@app/core/core.state'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { isDefinedAndNotNull } from '@core/utils'; +import { + phoneNumberPattern, + SmsProviderConfiguration, SmsProviderType, + TwilioSmsProviderConfiguration +} from '@shared/models/settings.models'; + +@Component({ + selector: 'tb-twilio-sms-provider-configuration', + templateUrl: './twilio-sms-provider-configuration.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TwilioSmsProviderConfigurationComponent), + multi: true + }] +}) +export class TwilioSmsProviderConfigurationComponent implements ControlValueAccessor, OnInit { + + twilioSmsProviderConfigurationFormGroup: FormGroup; + + phoneNumberPattern = phoneNumberPattern; + + private requiredValue: boolean; + + get required(): boolean { + return this.requiredValue; + } + + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + private fb: FormBuilder) { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.twilioSmsProviderConfigurationFormGroup = this.fb.group({ + numberFrom: [null, [Validators.required, Validators.pattern(phoneNumberPattern)]], + accountSid: [null, [Validators.required]], + accountToken: [null, [Validators.required]] + }); + this.twilioSmsProviderConfigurationFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.twilioSmsProviderConfigurationFormGroup.disable({emitEvent: false}); + } else { + this.twilioSmsProviderConfigurationFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: TwilioSmsProviderConfiguration | null): void { + if (isDefinedAndNotNull(value)) { + this.twilioSmsProviderConfigurationFormGroup.patchValue(value, {emitEvent: false}); + } + } + + private updateModel() { + let configuration: TwilioSmsProviderConfiguration = null; + if (this.twilioSmsProviderConfigurationFormGroup.valid) { + configuration = this.twilioSmsProviderConfigurationFormGroup.value; + (configuration as SmsProviderConfiguration).type = SmsProviderType.TWILIO; + } + this.propagateChange(configuration); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/action/custom-action.models.ts b/ui-ngx/src/app/modules/home/components/widget/action/custom-action.models.ts index 6539780e02..e15fc7f0eb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/custom-action.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/action/custom-action.models.ts @@ -71,7 +71,7 @@ const customPrettyActionCompletions: TbEditorCompletions = { } }, ...customActionCompletions -} +}; export const CustomActionEditorCompleter = new TbEditorCompleter(customActionCompletions); export const CustomPrettyActionEditorCompleter = new TbEditorCompleter(customPrettyActionCompletions); diff --git a/ui-ngx/src/app/modules/home/components/widget/action/custom-sample-html.raw b/ui-ngx/src/app/modules/home/components/widget/action/custom-sample-html.raw index 468d31d189..f42fcd5813 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/custom-sample-html.raw +++ b/ui-ngx/src/app/modules/home/components/widget/action/custom-sample-html.raw @@ -178,17 +178,17 @@ - - - - - + + + + + @@ -338,16 +338,16 @@ - - - - - + + + + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html index d269fbbe92..54ddd6fc80 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html @@ -61,7 +61,7 @@
- @@ -96,7 +96,7 @@ width: (actionCellDescriptors.length * 40) + 'px' }">
-
-
{{ column.title }} @@ -57,7 +57,7 @@ width: (actionCellDescriptors.length * 40) + 'px' }">
-
Timestamp @@ -67,7 +67,7 @@ width: (actionCellDescriptors.length * 40) + 'px' }">
- + + + +
+
+
+ + admin.number-to + + + {{ 'admin.number-to-required' | translate }} + + + {{ 'admin.phone-number-pattern' | translate }} + + + + + admin.sms-message + + + {{ 'admin.sms-message-required' | translate }} + + + {{ 'admin.sms-message-max-length' | translate }} + + +
+
+
+ + +
+ diff --git a/ui-ngx/src/app/modules/home/pages/admin/send-test-sms-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/admin/send-test-sms-dialog.component.ts new file mode 100644 index 0000000000..b29f48e15b --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/send-test-sms-dialog.component.ts @@ -0,0 +1,87 @@ +/// +/// Copyright © 2016-2020 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, Inject, OnInit } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Router } from '@angular/router'; +import { phoneNumberPattern, SmsProviderConfiguration, TestSmsRequest } from '@shared/models/settings.models'; +import { AdminService } from '@core/http/admin.service'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { TranslateService } from '@ngx-translate/core'; + +export interface SendTestSmsDialogData { + smsProviderConfiguration: SmsProviderConfiguration; +} + +@Component({ + selector: 'tb-send-test-sms-dialog', + templateUrl: './send-test-sms-dialog.component.html', + styleUrls: [] +}) +export class SendTestSmsDialogComponent extends + DialogComponent implements OnInit { + + phoneNumberPattern = phoneNumberPattern; + + sendTestSmsFormGroup: FormGroup; + + smsProviderConfiguration = this.data.smsProviderConfiguration; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: SendTestSmsDialogData, + private adminService: AdminService, + private translate: TranslateService, + public dialogRef: MatDialogRef, + public fb: FormBuilder) { + super(store, router, dialogRef); + } + + ngOnInit(): void { + this.sendTestSmsFormGroup = this.fb.group({ + numberTo: [null, [Validators.required, Validators.pattern(phoneNumberPattern)]], + message: [null, [Validators.required, Validators.maxLength(1600)]] + }); + } + + close(): void { + this.dialogRef.close(); + } + + sendTestSms(): void { + const request: TestSmsRequest = { + providerConfiguration: this.smsProviderConfiguration, + numberTo: this.sendTestSmsFormGroup.value.numberTo, + message: this.sendTestSmsFormGroup.value.message + }; + this.adminService.sendTestSms(request).subscribe( + () => { + this.store.dispatch(new ActionNotificationShow( + { + message: this.translate.instant('admin.test-sms-sent'), + target: 'sendTestSmsDialogContent', + verticalPosition: 'bottom', + horizontalPosition: 'left', + type: 'success' + })); + } + ); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/settings-card.scss b/ui-ngx/src/app/modules/home/pages/admin/settings-card.scss index 6e7c08532d..b22c91d5cf 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/settings-card.scss +++ b/ui-ngx/src/app/modules/home/pages/admin/settings-card.scss @@ -18,7 +18,12 @@ :host { mat-card.settings-card { margin: 8px; - @media #{$mat-gt-sm} { + + @media #{$mat-md} { + width: 80%; + } + + @media #{$mat-gt-md} { width: 60%; } } diff --git a/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.html b/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.html new file mode 100644 index 0000000000..426c3eda25 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.html @@ -0,0 +1,50 @@ + +
+ + +
+ admin.sms-provider-settings + +
+
+
+ + +
+ +
+
+ + +
+ + +
+
+ +
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.scss b/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.scss new file mode 100644 index 0000000000..bb3718c2da --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.scss @@ -0,0 +1,18 @@ +/** + * Copyright © 2016-2020 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 { + +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.ts b/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.ts new file mode 100644 index 0000000000..69510bd683 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.ts @@ -0,0 +1,95 @@ +/// +/// Copyright © 2016-2020 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 { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { PageComponent } from '@shared/components/page.component'; +import { Router } from '@angular/router'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { AdminSettings, SmsProviderConfiguration } from '@shared/models/settings.models'; +import { AdminService } from '@core/http/admin.service'; +import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; +import { MatDialog } from '@angular/material/dialog'; +import { SendTestSmsDialogComponent, SendTestSmsDialogData } from '@home/pages/admin/send-test-sms-dialog.component'; + +@Component({ + selector: 'tb-sms-provider', + templateUrl: './sms-provider.component.html', + styleUrls: ['./sms-provider.component.scss', './settings-card.scss'] +}) +export class SmsProviderComponent extends PageComponent implements OnInit, HasConfirmForm { + + smsProvider: FormGroup; + adminSettings: AdminSettings; + + constructor(protected store: Store, + private router: Router, + private adminService: AdminService, + private dialog: MatDialog, + public fb: FormBuilder) { + super(store); + } + + ngOnInit() { + this.buildSmsProviderForm(); + this.adminService.getAdminSettings('sms', {ignoreErrors: true}).subscribe( + (adminSettings) => { + this.adminSettings = adminSettings; + this.smsProvider.reset({configuration: this.adminSettings.jsonValue}); + }, + () => { + this.adminSettings = { + key: 'sms', + jsonValue: null + }; + this.smsProvider.reset({configuration: this.adminSettings.jsonValue}); + } + ); + } + + buildSmsProviderForm() { + this.smsProvider = this.fb.group({ + configuration: [null, [Validators.required]] + }); + this.registerDisableOnLoadFormControl(this.smsProvider.get('configuration')); + } + + sendTestSms(): void { + this.dialog.open(SendTestSmsDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + smsProviderConfiguration: this.smsProvider.value.configuration + } + }); + } + + save(): void { + this.adminSettings.jsonValue = this.smsProvider.value.configuration; + this.adminService.saveAdminSettings(this.adminSettings).subscribe( + (adminSettings) => { + this.adminSettings = adminSettings; + this.smsProvider.reset({configuration: this.adminSettings.jsonValue}); + } + ); + } + + confirmForm(): FormGroup { + return this.smsProvider; + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/api-usage/api-usage-routing.module.ts b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage-routing.module.ts new file mode 100644 index 0000000000..b6c6906c06 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage-routing.module.ts @@ -0,0 +1,41 @@ +/// +/// Copyright © 2016-2020 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 { RouterModule, Routes } from '@angular/router'; +import { Authority } from '@shared/models/authority.enum'; +import { ApiUsageComponent } from '@home/pages/api-usage/api-usage.component'; + +const routes: Routes = [ + { + path: 'usage', + component: ApiUsageComponent, + data: { + auth: [Authority.TENANT_ADMIN], + title: 'api-usage.api-usage', + breadcrumb: { + label: 'api-usage.api-usage', + icon: 'insert_chart' + } + } + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class ApiUsageRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.html b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.html new file mode 100644 index 0000000000..d7b188fa23 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.html @@ -0,0 +1,18 @@ + + diff --git a/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.scss b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.scss new file mode 100644 index 0000000000..342433682c --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.scss @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2020 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 { + width: 100%; + height: 100%; +} diff --git a/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.ts b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.ts new file mode 100644 index 0000000000..026d81fe27 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.component.ts @@ -0,0 +1,41 @@ +/// +/// Copyright © 2016-2020 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 { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { PageComponent } from '@shared/components/page.component'; +import apiUsageDashboardJson from '!raw-loader!./api_usage_json.raw'; +import { Dashboard } from '@shared/models/dashboard.models'; + +@Component({ + selector: 'tb-api-usage', + templateUrl: './api-usage.component.html', + styleUrls: ['./api-usage.component.scss'] +}) +export class ApiUsageComponent extends PageComponent implements OnInit { + + apiUsageDashboard: Dashboard; + + constructor(protected store: Store) { + super(store); + } + + ngOnInit() { + this.apiUsageDashboard = JSON.parse(apiUsageDashboardJson); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.module.ts b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.module.ts new file mode 100644 index 0000000000..ba6ec77d44 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/api-usage/api-usage.module.ts @@ -0,0 +1,38 @@ +/// +/// Copyright © 2016-2020 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 '@app/shared/shared.module'; +import { HomeComponentsModule } from '@modules/home/components/home-components.module'; +import { ApiUsageComponent } from '@home/pages/api-usage/api-usage.component'; +import { DashboardModule } from '@home/pages/dashboard/dashboard.module'; +import { ApiUsageRoutingModule } from '@home/pages/api-usage/api-usage-routing.module'; + +@NgModule({ + declarations: + [ + ApiUsageComponent + ], + imports: [ + CommonModule, + SharedModule, + HomeComponentsModule, + DashboardModule, + ApiUsageRoutingModule + ] +}) +export class ApiUsageModule { } diff --git a/ui-ngx/src/app/modules/home/pages/api-usage/api_usage_json.raw b/ui-ngx/src/app/modules/home/pages/api-usage/api_usage_json.raw new file mode 100644 index 0000000000..b54ce8ea99 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/api-usage/api_usage_json.raw @@ -0,0 +1,4420 @@ +{ + "title": "Api Usage", + "configuration": { + "description": "", + "widgets": { + "fd6df872-2ddf-0921-3929-2e7f55062fad": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "html_value_card", + "type": "latest", + "title": "New widget", + "sizeX": 7.5, + "sizeY": 3, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "jsExecutionApiState", + "type": "timeseries", + "label": "apiState", + "color": "#2196f3", + "settings": {}, + "_hash": 0.8830669138660703, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "jsExecutionLimit", + "type": "timeseries", + "label": "limit", + "color": "#4caf50", + "settings": {}, + "_hash": 0.5463603803546802, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "jsExecutionCount", + "type": "timeseries", + "label": "count", + "color": "#f44336", + "settings": {}, + "_hash": 0.5564241862015964, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "jsExecutionApiState", + "type": "timeseries", + "label": "apiStateClass", + "color": "#ffc107", + "settings": {}, + "_hash": 0.8737107059960671, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value ? value.toLowerCase() : '';" + }, + { + "name": "jsExecutionApiState", + "type": "timeseries", + "label": "cardId", + "color": "#607d8b", + "settings": {}, + "_hash": 0.051659774305067296, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return (Math.random()*100000).toFixed(0);" + }, + { + "name": "jsExecutionApiState", + "type": "timeseries", + "label": "title", + "color": "#9c27b0", + "settings": {}, + "_hash": 0.7673280949238444, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"{i18n:api-usage.javascript}\";" + }, + { + "name": "jsExecutionApiState", + "type": "timeseries", + "label": "unit", + "color": "#8bc34a", + "settings": {}, + "_hash": 0.7926918686567068, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"{i18n:api-usage.executions}\";" + } + ] + } + ], + "timewindow": { + "realtime": { + "timewindowMs": 60000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "#666666", + "padding": "0", + "settings": { + "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
${unit}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
", + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n" + }, + "title": "JavaScript functions", + "dropShadow": true, + "enableFullscreen": false, + "widgetStyle": { + "cursor": "default" + }, + "titleStyle": { + "fontSize": "20px", + "fontWeight": 400 + }, + "useDashboardTimewindow": true, + "showLegend": false, + "actions": { + "elementClick": [ + { + "name": "javascript_functions_details", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "javascript_functions", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "d4961bea-84de-e1af-e50f-666b98d34cd5" + } + ] + }, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "" + }, + "row": 0, + "col": 0, + "id": "fd6df872-2ddf-0921-3929-2e7f55062fad" + }, + "7e235874-461b-e7c2-2fdd-d8762a020773": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "html_value_card", + "type": "latest", + "title": "New widget", + "sizeX": 7.5, + "sizeY": 3, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "dbApiState", + "type": "timeseries", + "label": "apiState", + "color": "#2196f3", + "settings": {}, + "_hash": 0.8830669138660703, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "storageDataPointsLimit", + "type": "timeseries", + "label": "limit", + "color": "#4caf50", + "settings": {}, + "_hash": 0.5463603803546802, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "storageDataPointsCount", + "type": "timeseries", + "label": "count", + "color": "#f44336", + "settings": {}, + "_hash": 0.5564241862015964, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "dbApiState", + "type": "timeseries", + "label": "apiStateClass", + "color": "#ffc107", + "settings": {}, + "_hash": 0.8737107059960671, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value ? value.toLowerCase() : '';" + }, + { + "name": "dbApiState", + "type": "timeseries", + "label": "cardId", + "color": "#607d8b", + "settings": {}, + "_hash": 0.051659774305067296, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return (Math.random()*100000).toFixed(0);" + }, + { + "name": "dbApiState", + "type": "timeseries", + "label": "title", + "color": "#9c27b0", + "settings": {}, + "_hash": 0.6301889725474652, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"{i18n:api-usage.telemetry}\";" + }, + { + "name": "dbApiState", + "type": "timeseries", + "label": "unit", + "color": "#8bc34a", + "settings": {}, + "_hash": 0.0027742924142306613, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"{i18n:api-usage.data-points-storage-days}\";" + } + ] + } + ], + "timewindow": { + "realtime": { + "timewindowMs": 60000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "#666666", + "padding": "0", + "settings": { + "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
${unit}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
", + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n" + }, + "title": "Telemetry persistence", + "dropShadow": true, + "enableFullscreen": false, + "widgetStyle": { + "cursor": "default" + }, + "titleStyle": { + "fontSize": "20px", + "fontWeight": 400 + }, + "useDashboardTimewindow": true, + "showLegend": false, + "actions": { + "elementClick": [ + { + "name": "telemetry_persistence_details", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "telemetry_persistence", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "6248831c-5b3f-8879-8548-afcf43f10610" + } + ] + }, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "" + }, + "row": 0, + "col": 0, + "id": "7e235874-461b-e7c2-2fdd-d8762a020773" + }, + "08545554-a0e8-05c7-66df-6000cfeff8a4": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "html_value_card", + "type": "latest", + "title": "New widget", + "sizeX": 7.5, + "sizeY": 3, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "ruleEngineApiState", + "type": "timeseries", + "label": "apiState", + "color": "#2196f3", + "settings": {}, + "_hash": 0.8830669138660703, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "ruleEngineExecutionLimit", + "type": "timeseries", + "label": "limit", + "color": "#4caf50", + "settings": {}, + "_hash": 0.5463603803546802, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "ruleEngineExecutionCount", + "type": "timeseries", + "label": "count", + "color": "#f44336", + "settings": {}, + "_hash": 0.5564241862015964, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "ruleEngineApiState", + "type": "timeseries", + "label": "apiStateClass", + "color": "#ffc107", + "settings": {}, + "_hash": 0.8737107059960671, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value ? value.toLowerCase() : '';" + }, + { + "name": "ruleEngineApiState", + "type": "timeseries", + "label": "cardId", + "color": "#607d8b", + "settings": {}, + "_hash": 0.051659774305067296, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return (Math.random()*100000).toFixed(0);" + }, + { + "name": "ruleEngineApiState", + "type": "timeseries", + "label": "title", + "color": "#9c27b0", + "settings": {}, + "_hash": 0.3551317421302518, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"{i18n:api-usage.rule-engine}\";" + }, + { + "name": "ruleEngineApiState", + "type": "timeseries", + "label": "unit", + "color": "#8bc34a", + "settings": {}, + "_hash": 0.5100381746798048, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"{i18n:api-usage.executions}\";" + } + ] + } + ], + "timewindow": { + "realtime": { + "timewindowMs": 60000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "#666666", + "padding": "0", + "settings": { + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n", + "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
${unit}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n \n
\n
" + }, + "title": "Rule Engine execution", + "dropShadow": true, + "enableFullscreen": false, + "widgetStyle": { + "cursor": "default" + }, + "titleStyle": { + "fontSize": "20px", + "fontWeight": 400 + }, + "useDashboardTimewindow": true, + "showLegend": false, + "actions": { + "elementClick": [ + { + "name": "rule_engine_execution_details", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "rule_engine_execution", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "3c30248f-0cd8-fb97-a917-bc1e09984a79" + }, + { + "name": "rule_engine_statistics_details", + "icon": "show_chart", + "type": "openDashboardState", + "targetDashboardStateId": "rule_engine_statistics", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "04e4565a-9e24-23df-f376-f2ec70a8165f" + } + ] + }, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "" + }, + "row": 0, + "col": 0, + "id": "08545554-a0e8-05c7-66df-6000cfeff8a4" + }, + "a245c67e-53ec-d299-fa89-69fe2062ccb2": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "html_value_card", + "type": "latest", + "title": "New widget", + "sizeX": 7.5, + "sizeY": 3, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "transportApiState", + "type": "timeseries", + "label": "apiState", + "color": "#2196f3", + "settings": {}, + "_hash": 0.8830669138660703, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "transportMsgLimit", + "type": "timeseries", + "label": "limit", + "color": "#4caf50", + "settings": {}, + "_hash": 0.5463603803546802, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "transportMsgCount", + "type": "timeseries", + "label": "count", + "color": "#f44336", + "settings": {}, + "_hash": 0.5564241862015964, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "transportApiState", + "type": "timeseries", + "label": "apiStateClass", + "color": "#ffc107", + "settings": {}, + "_hash": 0.8737107059960671, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value ? value.toLowerCase() : '';" + }, + { + "name": "transportApiState", + "type": "timeseries", + "label": "cardId", + "color": "#607d8b", + "settings": {}, + "_hash": 0.051659774305067296, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return (Math.random()*100000).toFixed(0);" + }, + { + "name": "transportDataPointsLimit", + "type": "timeseries", + "label": "pointsLimit", + "color": "#9c27b0", + "settings": {}, + "_hash": 0.22082255831864894, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "transportDataPointsCount", + "type": "timeseries", + "label": "pointsCount", + "color": "#8bc34a", + "settings": {}, + "_hash": 0.6340356364819146, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "transportApiState", + "type": "timeseries", + "label": "title", + "color": "#3f51b5", + "settings": {}, + "_hash": 0.6894070537030252, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"{i18n:api-usage.transport}\";" + } + ] + } + ], + "timewindow": { + "realtime": { + "timewindowMs": 60000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "#666666", + "padding": "0", + "settings": { + "cardHtml": "
\n \n \n
\n
\n
\n
\n ${title}\n
\n
${apiState}
\n
\n
\n
\n
\n
{i18n:api-usage.messages}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
{i18n:api-usage.data-points}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
", + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bars-row {\n flex: 1;\n display: flex;\n flex-direction: row;\n}\n\n.card .bar-column {\n flex: 1;\n display: flex;\n flex-direction: column;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 6px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n" + }, + "title": "Transport", + "dropShadow": true, + "enableFullscreen": false, + "widgetStyle": { + "cursor": "default" + }, + "titleStyle": { + "fontSize": "20px", + "fontWeight": 400 + }, + "useDashboardTimewindow": true, + "showLegend": false, + "actions": { + "elementClick": [ + { + "name": "transport_details", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "transport", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "46b7cefe-e1f2-67c1-4055-3a214520f869" + } + ] + }, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "" + }, + "row": 0, + "col": 0, + "id": "a245c67e-53ec-d299-fa89-69fe2062ccb2" + }, + "a151ae60-0326-6116-d818-9070dda8e9c7": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "html_value_card", + "type": "latest", + "title": "New widget", + "sizeX": 7.5, + "sizeY": 3, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "emailApiState", + "type": "timeseries", + "label": "apiState", + "color": "#2196f3", + "settings": {}, + "_hash": 0.8830669138660703, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "emailLimit", + "type": "timeseries", + "label": "limit", + "color": "#4caf50", + "settings": {}, + "_hash": 0.5463603803546802, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "emailCount", + "type": "timeseries", + "label": "count", + "color": "#f44336", + "settings": {}, + "_hash": 0.5564241862015964, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "emailApiState", + "type": "timeseries", + "label": "apiStateClass", + "color": "#ffc107", + "settings": {}, + "_hash": 0.8737107059960671, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value ? value.toLowerCase() : '';" + }, + { + "name": "emailApiState", + "type": "timeseries", + "label": "cardId", + "color": "#607d8b", + "settings": {}, + "_hash": 0.051659774305067296, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return (Math.random()*100000).toFixed(0);" + }, + { + "name": "emailApiState", + "type": "timeseries", + "label": "title", + "color": "#9c27b0", + "settings": {}, + "_hash": 0.43439375716502227, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"{i18n:api-usage.email}\";" + }, + { + "name": "emailApiState", + "type": "timeseries", + "label": "unit", + "color": "#8bc34a", + "settings": {}, + "_hash": 0.9964061963495883, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"{i18n:api-usage.messages}\";" + } + ] + } + ], + "timewindow": { + "realtime": { + "timewindowMs": 60000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "#666666", + "padding": "0", + "settings": { + "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
${unit}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
", + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n" + }, + "title": "Email messages", + "dropShadow": true, + "enableFullscreen": false, + "widgetStyle": { + "cursor": "default" + }, + "titleStyle": { + "fontSize": "20px", + "fontWeight": 400 + }, + "useDashboardTimewindow": true, + "showLegend": false, + "actions": { + "elementClick": [ + { + "name": "email_messages_details", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "email_messages", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "946ba769-84ac-1507-6baa-94701de8967b" + } + ] + }, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "" + }, + "row": 0, + "col": 0, + "id": "a151ae60-0326-6116-d818-9070dda8e9c7" + }, + "2503a391-5692-0614-85e6-f179c5ee0dc9": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "html_value_card", + "type": "latest", + "title": "New widget", + "sizeX": 7.5, + "sizeY": 3, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "smsApiState", + "type": "timeseries", + "label": "apiState", + "color": "#2196f3", + "settings": {}, + "_hash": 0.8830669138660703, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "smsLimit", + "type": "timeseries", + "label": "limit", + "color": "#4caf50", + "settings": {}, + "_hash": 0.5463603803546802, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "smsCount", + "type": "timeseries", + "label": "count", + "color": "#f44336", + "settings": {}, + "_hash": 0.5564241862015964, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "smsApiState", + "type": "timeseries", + "label": "apiStateClass", + "color": "#ffc107", + "settings": {}, + "_hash": 0.8737107059960671, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return value ? value.toLowerCase() : '';" + }, + { + "name": "smsApiState", + "type": "timeseries", + "label": "cardId", + "color": "#607d8b", + "settings": {}, + "_hash": 0.051659774305067296, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return (Math.random()*100000).toFixed(0);" + }, + { + "name": "smsApiState", + "type": "timeseries", + "label": "title", + "color": "#9c27b0", + "settings": {}, + "_hash": 0.286155312985084, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"{i18n:api-usage.sms}\";" + }, + { + "name": "smsApiState", + "type": "timeseries", + "label": "unit", + "color": "#8bc34a", + "settings": {}, + "_hash": 0.39363366156190605, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "return \"{i18n:api-usage.messages}\";" + } + ] + } + ], + "timewindow": { + "realtime": { + "timewindowMs": 60000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "#666666", + "padding": "0", + "settings": { + "cardHtml": "
\n \n \n
\n
\n
\n
${title}
\n
${apiState}
\n
\n
\n
${unit}
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
", + "cardCss": ".card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n.card > img {\n height: 0;\n}\n\n.card .content {\n flex: 1; \n padding: 13px 13px 0;\n display: flex;\n box-sizing: border-box;\n}\n\n.card .content .column {\n display: flex;\n flex-direction: column; \n justify-content: space-around;\n flex: 1;\n}\n\n.card .content .title-row {\n display: flex;\n flex-direction: row;\n padding-bottom: 10px;\n}\n\n.card .title {\n flex: 1;\n font-size: 20px;\n font-weight: 400;\n color: #666666;\n}\n\n.card .state {\n text-transform: uppercase;\n font-size: 20px;\n font-weight: bold;\n}\n\n.card.enabled .state {\n color: #00B260;\n}\n\n.card.warning .state {\n color: #FFAD6F;\n}\n\n.card.disabled .state {\n color: #F73243;\n}\n\n.card .bar-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.card .bar {\n flex: 1;\n max-height: 30px;\n margin-top: 3.5px;\n margin-bottom: 4px;\n background-color: #F0F0F0;\n border: 1px solid #DADCDB;\n border-radius: 2px;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);\n}\n\n.card.enabled .bar {\n border-color: #00B260;\n background-color: #F0FBF7;\n}\n\n.card.warning .bar {\n border-color: #FFAD6F;\n background-color: #FFFAF6;\n}\n\n.card.disabled .bar {\n border-color: #F73243;\n background-color: #FFF0F0;\n}\n\n.card .bar .bar-fill {\n background-color: #F0F0F0;\n border-radius: 2px;\n height: 100%;\n width: 0%;\n}\n\n.card.enabled .bar-fill {\n background-color: #00C46C;\n}\n\n.card.warning .bar-fill {\n background-color: #FFD099;\n}\n\n.card.disabled .bar-fill {\n background-color: #FF9494;\n}\n\n.card .bar-labels {\n height: 20px;\n font-size: 16px;\n color: #666;\n display: flex;\n flex-direction: row;\n}\n\n\n.card .mat-button {\n text-transform: uppercase;\n}\n\n.card .mat-button-wrapper {\n pointer-events: none;\n}\n\n.card .action-row {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n padding: 8px 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .card .title {\n font-size: 12px;\n }\n .card .state {\n font-size: 12px;\n }\n .card .unit {\n font-size: 8px;\n }\n .card .bar-labels {\n font-size: 8px;\n }\n .card .mat-button {\n font-size: 8px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1280px) and (max-width: 1599px) {\n .card .title {\n font-size: 14px;\n }\n .card .state {\n font-size: 14px;\n }\n .card .unit {\n font-size: 10px;\n }\n .card .bar-labels {\n font-size: 10px;\n }\n .card .mat-button {\n font-size: 10px;\n }\n .card .action-row {\n padding: 0;\n }\n}\n\n@media screen and (min-width: 1600px) and (max-width: 1919px) {\n .card .title {\n font-size: 16px;\n }\n .card .state {\n font-size: 16px;\n }\n .card .unit {\n font-size: 12px;\n }\n .card .bar-labels {\n font-size: 12px;\n }\n .card .mat-button {\n font-size: 12px;\n }\n .card .action-row {\n padding: 0;\n }\n} \n\n\n" + }, + "title": "SMS messages", + "dropShadow": true, + "enableFullscreen": false, + "widgetStyle": { + "cursor": "default" + }, + "titleStyle": { + "fontSize": "20px", + "fontWeight": 400 + }, + "useDashboardTimewindow": true, + "showLegend": false, + "actions": { + "elementClick": [ + { + "name": "sms_messages_details", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "sms_messages", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "73a35536-96a3-47dd-95bb-b1d9119d0ea2" + } + ] + }, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "" + }, + "row": 0, + "col": 0, + "id": "2503a391-5692-0614-85e6-f179c5ee0dc9" + }, + "68e16e98-0420-f72c-4848-41dedffd3904": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "ruleEngineExecutionCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.rule-engine-executions}", + "color": "#ab00ff", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 86400000, + "interval": 3600000 + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "min": 0, + "tickDecimals": 0 + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 2400000, + "barAlignment": "left", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "{i18n:api-usage.rule-engine-hourly-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": { + "headerButton": [ + { + "name": "{i18n:api-usage.view-statistics}", + "icon": "show_chart", + "type": "openDashboardState", + "targetDashboardStateId": "rule_engine_statistics", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "f9f08190-9ed9-d802-5b7a-c57ff84b5648" + }, + { + "name": "{i18n:api-usage.view-details}", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "rule_engine_execution", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "1aec196b-44ba-ddf4-c4dc-c3f60c1eb6fc" + } + ] + }, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "68e16e98-0420-f72c-4848-41dedffd3904" + }, + "2aa6b499-6e27-b315-6833-89c4d58485ce": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "transportMsgCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.transport-messages}", + "color": "#2196f3", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "transportDataPointsCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.transport-data-points}", + "color": "#4caf50", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.46849996721308895, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 86400000, + "interval": 3600000 + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "min": 0, + "tickDecimals": 0 + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": true, + "tooltipIndividual": false, + "defaultBarWidth": 2400000, + "barAlignment": "left", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + }, + "tooltipCumulative": false + }, + "title": "{i18n:api-usage.transport-hourly-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": { + "headerButton": [ + { + "name": "{i18n:api-usage.view-details}", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "transport", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "6ef12f6a-0266-25cf-6ca5-5dcb772252c6" + } + ] + }, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "2aa6b499-6e27-b315-6833-89c4d58485ce" + }, + "d890cea3-fba0-6474-9a21-fa780230dc62": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "jsExecutionCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.javascript-executions}", + "color": "#ff9900", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 86400000, + "interval": 3600000 + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "min": 0, + "tickDecimals": 0 + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 2400000, + "barAlignment": "left", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "{i18n:api-usage.javascript-functions-hourly-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": { + "headerButton": [ + { + "name": "{i18n:api-usage.view-details}", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "javascript_functions", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "4687d3f6-8800-a3b6-26e5-0d33f3b828a9" + } + ] + }, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "d890cea3-fba0-6474-9a21-fa780230dc62" + }, + "84b6cfa5-1449-e0f2-3560-f810d2dd7ead": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "storageDataPointsCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.data-points-storage-days}", + "color": "#1039ee", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 86400000, + "interval": 3600000 + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "min": 0, + "tickDecimals": 0 + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 2400000, + "barAlignment": "left", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "{i18n:api-usage.telemetry-persistence-hourly-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": { + "headerButton": [ + { + "name": "{i18n:api-usage.view-details}", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "telemetry_persistence", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "16707efb-e572-bd02-c219-55fc1b0f672a" + } + ] + }, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "84b6cfa5-1449-e0f2-3560-f810d2dd7ead" + }, + "d296b566-a000-7402-ae9d-c815381c5435": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "emailCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.email-messages}", + "color": "#d35a00", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 86400000, + "interval": 3600000 + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "min": 0, + "tickDecimals": 0 + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 2400000, + "barAlignment": "left", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "{i18n:api-usage.email-messages-hourly-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": { + "headerButton": [ + { + "name": "{i18n:api-usage.view-details}", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "email_messages", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "371882f9-ea23-3abc-fca8-9449c5dfdd6b" + } + ] + }, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "d296b566-a000-7402-ae9d-c815381c5435" + }, + "00a02464-9509-911b-3b5e-21fb37629822": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "smsCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.sms-messages}", + "color": "#f36021", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 86400000, + "interval": 3600000 + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "min": 0, + "tickDecimals": 0 + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 2400000, + "barAlignment": "left", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "{i18n:api-usage.sms-messages-hourly-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": { + "headerButton": [ + { + "name": "{i18n:api-usage.view-details}", + "icon": "insert_chart", + "type": "openDashboardState", + "targetDashboardStateId": "sms_messages", + "setEntityId": false, + "stateEntityParamName": null, + "openRightLayout": false, + "id": "49aefac0-ec5e-d6f3-f39c-8744759f4b19" + } + ] + }, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "00a02464-9509-911b-3b5e-21fb37629822" + }, + "74199074-7873-6c6a-2a51-3fc614769f03": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "ruleEngineExecutionCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.rule-engine-executions}", + "color": "#ab00ff", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 2592000000, + "interval": 86400000 + }, + "aggregation": { + "type": "SUM", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "min": 0, + "tickDecimals": 0 + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 1800000, + "barAlignment": "left", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "{i18n:api-usage.rule-engine-daily-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": {}, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "74199074-7873-6c6a-2a51-3fc614769f03" + }, + "00006bc3-8a8d-b55e-ed39-e318f1bcd090": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "ruleEngineExecutionCount", + "type": "timeseries", + "label": "{i18n:api-usage.rule-engine-executions}", + "color": "#ab00ff", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 31536000000, + "interval": 1000 + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "min": 0, + "tickDecimals": 0 + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 900000000, + "barAlignment": "left", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "{i18n:api-usage.rule-engine-monthly-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": {}, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "00006bc3-8a8d-b55e-ed39-e318f1bcd090" + }, + "5f5ca59c-e507-5301-5910-7ad8cd34df40": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "jsExecutionCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.javascript-executions}", + "color": "#ff9900", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 2592000000, + "interval": 86400000 + }, + "aggregation": { + "type": "SUM", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "min": 0, + "tickDecimals": 0, + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 1800000, + "barAlignment": "left", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "{i18n:api-usage.javascript-functions-daily-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": {}, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "5f5ca59c-e507-5301-5910-7ad8cd34df40" + }, + "ada32ee9-44ed-48d1-c368-fd0c94b7607f": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "jsExecutionCount", + "type": "timeseries", + "label": "{i18n:api-usage.javascript-executions}", + "color": "#ff9900", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 31536000000, + "interval": 1000 + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "min": 0, + "tickDecimals": 0, + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 900000000, + "barAlignment": "left", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "{i18n:api-usage.javascript-functions-monthly-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": {}, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "ada32ee9-44ed-48d1-c368-fd0c94b7607f" + }, + "98bae68b-0f35-72f2-a428-9b06889f1554": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "transportMsgCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.transport-messages}", + "color": "#2196f3", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "transportDataPointsCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.transport-data-points}", + "color": "#4caf50", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.46849996721308895, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 2592000000, + "interval": 86400000 + }, + "aggregation": { + "type": "SUM", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "min": 0, + "tickDecimals": 0 + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": true, + "tooltipIndividual": false, + "defaultBarWidth": 1800000, + "barAlignment": "left", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + }, + "tooltipCumulative": false + }, + "title": "{i18n:api-usage.transport-daily-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": {}, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "98bae68b-0f35-72f2-a428-9b06889f1554" + }, + "e61e5477-5a09-cc25-966b-f613d81da833": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "transportMsgCount", + "type": "timeseries", + "label": "{i18n:api-usage.transport-messages}", + "color": "#2196f3", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "transportDataPointsCount", + "type": "timeseries", + "label": "{i18n:api-usage.transport-data-points}", + "color": "#4caf50", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.46849996721308895, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 31536000000, + "interval": 2592000000 + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;", + "min": 0, + "tickDecimals": 0 + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": true, + "tooltipIndividual": false, + "defaultBarWidth": 900000000, + "barAlignment": "left", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + }, + "tooltipCumulative": false + }, + "title": "{i18n:api-usage.transport-monthly-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": {}, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "e61e5477-5a09-cc25-966b-f613d81da833" + }, + "85fe0738-5326-f069-ab3f-30594bde5fed": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "storageDataPointsCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.data-points-storage-days}", + "color": "#1039ee", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 2592000000, + "interval": 86400000 + }, + "aggregation": { + "type": "SUM", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "min": 0, + "tickDecimals": 0, + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 1800000, + "barAlignment": "left", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "{i18n:api-usage.telemetry-persistence-daily-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": {}, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "85fe0738-5326-f069-ab3f-30594bde5fed" + }, + "eaeb381a-437e-f6e9-60c9-6bc8826fdd44": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "storageDataPointsCount", + "type": "timeseries", + "label": "{i18n:api-usage.data-points-storage-days}", + "color": "#1039ee", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 31536000000, + "interval": 1000 + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "min": 0, + "tickDecimals": 0, + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 900000000, + "barAlignment": "left", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "{i18n:api-usage.telemetry-persistence-monthly-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": {}, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "eaeb381a-437e-f6e9-60c9-6bc8826fdd44" + }, + "4b798823-b97d-9d6a-59dc-fcafd897fc23": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "emailCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.email-messages}", + "color": "#d35a00", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 2592000000, + "interval": 86400000 + }, + "aggregation": { + "type": "SUM", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "min": 0, + "tickDecimals": 0, + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 1800000, + "barAlignment": "left", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "{i18n:api-usage.email-messages-daily-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": {}, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "4b798823-b97d-9d6a-59dc-fcafd897fc23" + }, + "6a981580-7490-19dd-f937-b64cbf67a982": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "emailCount", + "type": "timeseries", + "label": "{i18n:api-usage.email-messages}", + "color": "#d35a00", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 31536000000, + "interval": 1000 + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "min": 0, + "tickDecimals": 0, + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 900000000, + "barAlignment": "left", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "{i18n:api-usage.sms-messages-monthly-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": {}, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "6a981580-7490-19dd-f937-b64cbf67a982" + }, + "7302df65-1b0c-579e-bbdb-145126ae3392": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "smsCountHourly", + "type": "timeseries", + "label": "{i18n:api-usage.sms-messages}", + "color": "#f36021", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 2592000000, + "interval": 86400000 + }, + "aggregation": { + "type": "SUM", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "min": 0, + "tickDecimals": 0, + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 1800000, + "barAlignment": "left", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "{i18n:api-usage.sms-messages-daily-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": {}, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "7302df65-1b0c-579e-bbdb-145126ae3392" + }, + "fdb385e7-14fe-fc9f-ebdc-b400f26fc66b": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "timeseries_bars_flot", + "type": "timeseries", + "title": "New widget", + "sizeX": 8, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "40193437-33ac-3172-eefd-0b08eb849062", + "filterId": null, + "dataKeys": [ + { + "name": "smsCount", + "type": "timeseries", + "label": "{i18n:api-usage.sms-messages}", + "color": "#f36021", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": false, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.0661644137210089, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 1, + "history": { + "historyType": 0, + "timewindowMs": 31536000000, + "interval": 1000 + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "min": 0, + "tickDecimals": 0, + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "defaultBarWidth": 900000000, + "barAlignment": "left", + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "{i18n:api-usage.sms-messages-monthly-activity}", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "widgetStyle": {}, + "useDashboardTimewindow": false, + "showLegend": true, + "actions": {}, + "displayTimewindow": true, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } + }, + "row": 0, + "col": 0, + "id": "fdb385e7-14fe-fc9f-ebdc-b400f26fc66b" + }, + "2408ad30-163e-8221-08e1-a82b638be564": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "basic_timeseries", + "type": "timeseries", + "title": "New widget", + "sizeX": 12, + "sizeY": 7, + "config": { + "datasources": [ + { + "type": "entity", + "dataKeys": [ + { + "name": "successfulMsgs", + "type": "timeseries", + "label": "{i18n:api-usage.successful}", + "color": "#4caf50", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": true, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.15490750967648736 + }, + { + "name": "failedMsgs", + "type": "timeseries", + "label": "{i18n:api-usage.permanent-failures}", + "color": "#ef5350", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": true, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.4186621166514697 + }, + { + "name": "tmpFailed", + "type": "timeseries", + "label": "{i18n:api-usage.processing-failures}", + "color": "#ffc107", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": true, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.49891007198715376 + } + ], + "entityAliasId": "2e4c97b0-257a-a1b9-690c-141d9bf2ec6f" + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 3600000, + "interval": 1000 + }, + "aggregation": { + "type": "NONE", + "limit": 10000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "min": 0, + "tickDecimals": 0, + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "Queue Stats", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "mobileHeight": null, + "showTitleIcon": false, + "titleIcon": null, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "widgetStyle": {}, + "useDashboardTimewindow": false, + "displayTimewindow": true, + "showLegend": true, + "actions": {}, + "legendConfig": { + "direction": "column", + "position": "bottom", + "showMin": true, + "showMax": true, + "showAvg": false, + "showTotal": true + } + }, + "id": "2408ad30-163e-8221-08e1-a82b638be564" + }, + "e43dcfe1-b970-6a11-ce0e-5769f3eb5e88": { + "isSystemType": true, + "bundleAlias": "charts", + "typeAlias": "basic_timeseries", + "type": "timeseries", + "title": "New widget", + "sizeX": 12, + "sizeY": 7, + "config": { + "datasources": [ + { + "type": "entity", + "dataKeys": [ + { + "name": "timeoutMsgs", + "type": "timeseries", + "label": "{i18n:api-usage.permanent-timeouts}", + "color": "#4caf50", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": true, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.565222981550328 + }, + { + "name": "tmpTimeout", + "type": "timeseries", + "label": "{i18n:api-usage.processing-timeouts}", + "color": "#9c27b0", + "settings": { + "excludeFromStacking": false, + "hideDataByDefault": false, + "disableDataHiding": false, + "removeFromLegend": false, + "showLines": true, + "fillLines": false, + "showPoints": false, + "showPointShape": "circle", + "pointShapeFormatter": "", + "showPointsLineWidth": 5, + "showPointsRadius": 3, + "showSeparateAxis": false, + "axisPosition": "left", + "thresholds": [ + { + "thresholdValueSource": "predefinedValue" + } + ], + "comparisonSettings": { + "showValuesForComparison": true + } + }, + "_hash": 0.2679547062508352 + } + ], + "entityAliasId": "2e4c97b0-257a-a1b9-690c-141d9bf2ec6f" + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 3600000, + "interval": 1000 + }, + "aggregation": { + "type": "NONE", + "limit": 10000 + } + }, + "showTitle": true, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "shadowSize": 4, + "fontColor": "#545454", + "fontSize": 10, + "xaxis": { + "showLabels": true, + "color": "#545454" + }, + "yaxis": { + "showLabels": true, + "color": "#545454", + "min": 0, + "tickDecimals": 0, + "ticksFormatter": "var rounder = Math.pow(10, 1);\nvar powers = [\n {key: 'Q', value: Math.pow(10, 15)},\n {key: 'T', value: Math.pow(10, 12)},\n {key: 'B', value: Math.pow(10, 9)},\n {key: 'M', value: Math.pow(10, 6)},\n {key: 'K', value: 1000}\n];\n\nvar key = '';\n\nfor (var i = 0; i < powers.length; i++) {\n var reduced = value / powers[i].value;\n reduced = Math.round(reduced * rounder) / rounder;\n if (reduced >= 1) {\n value = reduced;\n key = powers[i].key;\n break;\n }\n}\nreturn value + key;" + }, + "grid": { + "color": "#545454", + "tickColor": "#DDDDDD", + "verticalLines": true, + "horizontalLines": true, + "outlineWidth": 1 + }, + "stack": false, + "tooltipIndividual": false, + "timeForComparison": "months", + "xaxisSecond": { + "axisPosition": "top", + "showLabels": true + } + }, + "title": "Processing Failures and Timeouts", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "mobileHeight": null, + "showTitleIcon": false, + "titleIcon": null, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "widgetStyle": {}, + "useDashboardTimewindow": false, + "displayTimewindow": true, + "showLegend": true, + "actions": {}, + "legendConfig": { + "direction": "column", + "position": "bottom", + "showMin": true, + "showMax": true, + "showAvg": false, + "showTotal": true + } + }, + "id": "e43dcfe1-b970-6a11-ce0e-5769f3eb5e88" + }, + "a669cf86-e715-efa4-dd9a-b839abf499e9": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "timeseries_table", + "type": "timeseries", + "title": "New widget", + "sizeX": 24, + "sizeY": 5, + "config": { + "datasources": [ + { + "type": "entity", + "dataKeys": [ + { + "name": "ruleEngineException", + "type": "timeseries", + "label": "Rule Chain", + "color": "#2196f3", + "settings": { + "useCellStyleFunction": false, + "useCellContentFunction": true, + "cellContentFunction": "return JSON.parse(value).ruleChainName;" + }, + "_hash": 0.9954481282345906 + }, + { + "name": "ruleEngineException", + "type": "timeseries", + "label": "Rule Node", + "color": "#4caf50", + "settings": { + "useCellStyleFunction": false, + "useCellContentFunction": true, + "cellContentFunction": "return JSON.parse(value).ruleNodeName;" + }, + "_hash": 0.18580357036589978 + }, + { + "name": "ruleEngineException", + "type": "timeseries", + "label": "Latest Error", + "color": "#f44336", + "settings": { + "useCellStyleFunction": false, + "useCellContentFunction": true, + "cellContentFunction": "return JSON.parse(value).message;" + }, + "_hash": 0.7255162989552142 + } + ], + "entityAliasId": "2e4c97b0-257a-a1b9-690c-141d9bf2ec6f" + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 2592000000, + "interval": 1000 + }, + "aggregation": { + "type": "NONE", + "limit": 1000 + } + }, + "showTitle": true, + "backgroundColor": "rgb(255, 255, 255)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "showTimestamp": true, + "displayPagination": true, + "defaultPageSize": 10 + }, + "title": "Exceptions", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "useDashboardTimewindow": false, + "showLegend": false, + "widgetStyle": {}, + "actions": {}, + "showTitleIcon": false, + "titleIcon": null, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "displayTimewindow": true + }, + "id": "a669cf86-e715-efa4-dd9a-b839abf499e9" + } + }, + "states": { + "default": { + "name": "{i18n:api-usage.api-usage}", + "root": true, + "layouts": { + "main": { + "widgets": { + "fd6df872-2ddf-0921-3929-2e7f55062fad": { + "sizeX": 4, + "sizeY": 2, + "row": 0, + "col": 8, + "mobileHeight": 3 + }, + "7e235874-461b-e7c2-2fdd-d8762a020773": { + "sizeX": 4, + "sizeY": 2, + "row": 0, + "col": 12, + "mobileHeight": 3 + }, + "08545554-a0e8-05c7-66df-6000cfeff8a4": { + "sizeX": 4, + "sizeY": 2, + "row": 0, + "col": 4, + "mobileHeight": 3 + }, + "a245c67e-53ec-d299-fa89-69fe2062ccb2": { + "sizeX": 4, + "sizeY": 2, + "row": 0, + "col": 0, + "mobileHeight": 3 + }, + "a151ae60-0326-6116-d818-9070dda8e9c7": { + "sizeX": 4, + "sizeY": 2, + "row": 0, + "col": 16, + "mobileHeight": 3 + }, + "2503a391-5692-0614-85e6-f179c5ee0dc9": { + "sizeX": 4, + "sizeY": 2, + "row": 0, + "col": 20, + "mobileHeight": 3 + }, + "68e16e98-0420-f72c-4848-41dedffd3904": { + "sizeX": 8, + "sizeY": 4, + "row": 2, + "col": 8 + }, + "2aa6b499-6e27-b315-6833-89c4d58485ce": { + "sizeX": 8, + "sizeY": 4, + "row": 2, + "col": 0 + }, + "d890cea3-fba0-6474-9a21-fa780230dc62": { + "sizeX": 8, + "sizeY": 4, + "row": 2, + "col": 16 + }, + "84b6cfa5-1449-e0f2-3560-f810d2dd7ead": { + "sizeX": 8, + "sizeY": 4, + "row": 6, + "col": 0 + }, + "d296b566-a000-7402-ae9d-c815381c5435": { + "sizeX": 8, + "sizeY": 4, + "row": 6, + "col": 8 + }, + "00a02464-9509-911b-3b5e-21fb37629822": { + "sizeX": 8, + "sizeY": 4, + "row": 6, + "col": 16 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 5, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70 + } + } + } + }, + "transport": { + "name": "{i18n:api-usage.transport}", + "root": false, + "layouts": { + "main": { + "widgets": { + "98bae68b-0f35-72f2-a428-9b06889f1554": { + "sizeX": 24, + "sizeY": 6, + "row": 0, + "col": 0 + }, + "e61e5477-5a09-cc25-966b-f613d81da833": { + "sizeX": 24, + "sizeY": 6, + "row": 6, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70 + } + } + } + }, + "rule_engine_execution": { + "name": "{i18n:api-usage.rule-engine-executions}", + "root": false, + "layouts": { + "main": { + "widgets": { + "74199074-7873-6c6a-2a51-3fc614769f03": { + "sizeX": 24, + "sizeY": 6, + "row": 0, + "col": 0 + }, + "00006bc3-8a8d-b55e-ed39-e318f1bcd090": { + "sizeX": 24, + "sizeY": 6, + "row": 6, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70 + } + } + } + }, + "javascript_functions": { + "name": "{i18n:api-usage.javascript-functions}", + "root": false, + "layouts": { + "main": { + "widgets": { + "5f5ca59c-e507-5301-5910-7ad8cd34df40": { + "sizeX": 24, + "sizeY": 6, + "row": 0, + "col": 0 + }, + "ada32ee9-44ed-48d1-c368-fd0c94b7607f": { + "sizeX": 24, + "sizeY": 6, + "row": 6, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70 + } + } + } + }, + "telemetry_persistence": { + "name": "{i18n:api-usage.telemetry-persistence}", + "root": false, + "layouts": { + "main": { + "widgets": { + "85fe0738-5326-f069-ab3f-30594bde5fed": { + "sizeX": 24, + "sizeY": 6, + "row": 0, + "col": 0 + }, + "eaeb381a-437e-f6e9-60c9-6bc8826fdd44": { + "sizeX": 24, + "sizeY": 6, + "row": 6, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70 + } + } + } + }, + "email_messages": { + "name": "{i18n:api-usage.email-messages}", + "root": false, + "layouts": { + "main": { + "widgets": { + "4b798823-b97d-9d6a-59dc-fcafd897fc23": { + "sizeX": 24, + "sizeY": 6, + "row": 0, + "col": 0 + }, + "6a981580-7490-19dd-f937-b64cbf67a982": { + "sizeX": 24, + "sizeY": 6, + "row": 6, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70 + } + } + } + }, + "sms_messages": { + "name": "{i18n:api-usage.sms-messages}", + "root": false, + "layouts": { + "main": { + "widgets": { + "7302df65-1b0c-579e-bbdb-145126ae3392": { + "sizeX": 24, + "sizeY": 6, + "row": 0, + "col": 0 + }, + "fdb385e7-14fe-fc9f-ebdc-b400f26fc66b": { + "sizeX": 24, + "sizeY": 6, + "row": 6, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70 + } + } + } + }, + "rule_engine_statistics": { + "name": "{i18n:api-usage.rule-engine-statistics}", + "root": false, + "layouts": { + "main": { + "widgets": { + "2408ad30-163e-8221-08e1-a82b638be564": { + "sizeX": 12, + "sizeY": 7, + "mobileHeight": null, + "row": 0, + "col": 0 + }, + "e43dcfe1-b970-6a11-ce0e-5769f3eb5e88": { + "sizeX": 12, + "sizeY": 7, + "mobileHeight": null, + "row": 0, + "col": 12 + }, + "a669cf86-e715-efa4-dd9a-b839abf499e9": { + "sizeX": 24, + "sizeY": 5, + "row": 7, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70 + } + } + } + } + }, + "entityAliases": { + "40193437-33ac-3172-eefd-0b08eb849062": { + "id": "40193437-33ac-3172-eefd-0b08eb849062", + "alias": "Api usage state", + "filter": { + "type": "apiUsageState", + "resolveMultiple": false + } + }, + "2e4c97b0-257a-a1b9-690c-141d9bf2ec6f": { + "id": "2e4c97b0-257a-a1b9-690c-141d9bf2ec6f", + "alias": "TbServiceQueues", + "filter": { + "type": "assetType", + "resolveMultiple": true, + "assetType": "TbServiceQueue", + "assetNameFilter": "" + } + } + }, + "filters": {}, + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "selectedTab": 0, + "realtime": { + "timewindowMs": 86400000, + "interval": 3600000 + }, + "aggregation": { + "type": "NONE", + "limit": 50000 + } + }, + "settings": { + "stateControllerId": "entity", + "showTitle": false, + "showDashboardsSelect": false, + "showEntitiesSelect": false, + "showDashboardTimewindow": false, + "showDashboardExport": false, + "toolbarAlwaysOpen": true, + "titleColor": "rgba(0,0,0,0.870588)", + "showFilters": false + } + }, + "name": "Api Usage" +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html index 9f46f6b219..e6063b3556 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html @@ -196,7 +196,7 @@ matTooltipPosition="above"> done - - + +
diff --git a/ui-ngx/src/app/modules/home/pages/device/device.component.ts b/ui-ngx/src/app/modules/home/pages/device/device.component.ts index 833919eb78..f092d71fc1 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device.component.ts @@ -21,10 +21,9 @@ import { EntityComponent } from '../../components/entity/entity.component'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { createDeviceConfiguration, - createDeviceProfileConfiguration, createDeviceTransportConfiguration, + createDeviceTransportConfiguration, DeviceCredentials, DeviceData, DeviceInfo, - DeviceProfileData, DeviceProfileInfo, DeviceProfileType, DeviceTransportType @@ -33,9 +32,8 @@ import { EntityType } from '@shared/models/entity-type.models'; import { NULL_UUID } from '@shared/models/id/has-uuid'; import { ActionNotificationShow } from '@core/notification/notification.actions'; import { TranslateService } from '@ngx-translate/core'; -import { DeviceService } from '@core/http/device.service'; -import { ClipboardService } from 'ngx-clipboard'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; +import { Subject } from 'rxjs'; @Component({ selector: 'tb-device', @@ -46,12 +44,12 @@ export class DeviceComponent extends EntityComponent { entityType = EntityType; + deviceCredentials$: Subject; + deviceScope: 'tenant' | 'customer' | 'customer_user'; constructor(protected store: Store, protected translate: TranslateService, - private deviceService: DeviceService, - private clipboardService: ClipboardService, @Inject('entity') protected entityValue: DeviceInfo, @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, public fb: FormBuilder) { @@ -60,6 +58,7 @@ export class DeviceComponent extends EntityComponent { ngOnInit() { this.deviceScope = this.entitiesTableConfig.componentsData.deviceScope; + this.deviceCredentials$ = this.entitiesTableConfigValue.componentsData.deviceCredentials$; super.ngOnInit(); } @@ -114,26 +113,6 @@ export class DeviceComponent extends EntityComponent { })); } - copyAccessToken($event) { - if (this.entity.id) { - this.deviceService.getDeviceCredentials(this.entity.id.id, true).subscribe( - (deviceCredentials) => { - const credentialsId = deviceCredentials.credentialsId; - if (this.clipboardService.copyFromContent(credentialsId)) { - this.store.dispatch(new ActionNotificationShow( - { - message: this.translate.instant('device.accessTokenCopiedMessage'), - type: 'success', - duration: 750, - verticalPosition: 'bottom', - horizontalPosition: 'right' - })); - } - } - ); - } - } - onDeviceProfileUpdated() { this.entitiesTableConfig.table.updateData(false); } diff --git a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts index 0aaf8e62e7..c2671459a7 100644 --- a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts @@ -32,7 +32,7 @@ import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared import { AddEntityDialogData, EntityAction } from '@home/models/entity/entity-component.models'; import { Device, DeviceCredentials, DeviceInfo } from '@app/shared/models/device.models'; import { DeviceComponent } from '@modules/home/pages/device/device.component'; -import { forkJoin, Observable, of } from 'rxjs'; +import { forkJoin, Observable, of, Subject } from 'rxjs'; import { select, Store } from '@ngrx/store'; import { selectAuthUser } from '@core/auth/auth.selectors'; import { map, mergeMap, take, tap } from 'rxjs/operators'; @@ -63,6 +63,7 @@ import { DeviceTabsComponent } from '@home/pages/device/device-tabs.component'; import { HomeDialogsService } from '@home/dialogs/home-dialogs.service'; import { DeviceWizardDialogComponent } from '@home/components/wizard/device-wizard-dialog.component'; import { BaseData, HasId } from '@shared/models/base-data'; +import { isDefinedAndNotNull } from '@core/utils'; @Injectable() export class DevicesTableConfigResolver implements Resolve> { @@ -115,7 +116,8 @@ export class DevicesTableConfigResolver implements Resolve() }; this.customerId = routeParams.customerId; return this.store.pipe(select(selectAuthUser), take(1)).pipe( @@ -479,6 +481,10 @@ export class DevicesTableConfigResolver implements Resolve { + if (isDefinedAndNotNull(deviceCredentials)) { + this.config.componentsData.deviceCredentials$.next(deviceCredentials); + } }); } diff --git a/ui-ngx/src/app/modules/home/pages/home-pages.module.ts b/ui-ngx/src/app/modules/home/pages/home-pages.module.ts index 497c6231f3..a08edb5d32 100644 --- a/ui-ngx/src/app/modules/home/pages/home-pages.module.ts +++ b/ui-ngx/src/app/modules/home/pages/home-pages.module.ts @@ -33,6 +33,7 @@ import { TenantProfileModule } from './tenant-profile/tenant-profile.module'; import { MODULES_MAP } from '@shared/public-api'; import { modulesMap } from '../../common/modules-map'; import { DeviceProfileModule } from './device-profile/device-profile.module'; +import { ApiUsageModule } from '@home/pages/api-usage/api-usage.module'; @NgModule({ exports: [ @@ -50,6 +51,7 @@ import { DeviceProfileModule } from './device-profile/device-profile.module'; WidgetLibraryModule, DashboardModule, AuditLogModule, + ApiUsageModule, UserModule ], providers: [ diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html index fd4efddf5a..880e7586df 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html @@ -21,48 +21,46 @@ {{'rulechain.open-rulechain' | translate }}
-
-
-
-
-
- - rulenode.name - - - {{ 'rulenode.name-required' | translate }} - - - - {{ 'rulenode.debug-mode' | translate }} - -
- - -
- - rulenode.description - - -
+ +
+
+
+ + rulenode.name + + + {{ 'rulenode.name-required' | translate }} + + + + {{ 'rulenode.debug-mode' | translate }} +
-
- - -
- - rulenode.description - - -
-
-
- -
+ + +
+ + rulenode.description + + +
+ +
+ + +
+ + rulenode.description + + +
+
+ + diff --git a/ui-ngx/src/app/modules/home/pages/user/activation-link-dialog.component.html b/ui-ngx/src/app/modules/home/pages/user/activation-link-dialog.component.html index 52d221c55f..0221d412c8 100644 --- a/ui-ngx/src/app/modules/home/pages/user/activation-link-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/user/activation-link-dialog.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+

user.activation-link

diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.scss b/ui-ngx/src/app/modules/login/pages/login/login.component.scss index b8fa052e75..28150044c3 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.scss +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.scss @@ -21,7 +21,7 @@ .tb-login-content { margin-top: 36px; margin-bottom: 76px; - background-color: rgb(250, 250, 250); + background-color: #eee; .tb-login-form { @media #{$mat-gt-xs} { width: 550px !important; diff --git a/ui-ngx/src/app/shared/models/ace/service-completion.models.ts b/ui-ngx/src/app/shared/models/ace/service-completion.models.ts index f9b42f12f5..27a42680e5 100644 --- a/ui-ngx/src/app/shared/models/ace/service-completion.models.ts +++ b/ui-ngx/src/app/shared/models/ace/service-completion.models.ts @@ -56,6 +56,12 @@ export const customerHref = '
Attribute Data'; +export const timeseriesDataHref = 'Timeseries Data'; + +export const aggregationTypeHref = 'Aggregation Type'; + +export const dataSortOrderHref = 'Data Sort Order'; + export const userHref = 'User'; export const entityDataHref = 'Entity data'; @@ -1080,6 +1086,23 @@ export const serviceCompletions: TbEditorCompletions = { ], return: observableReturnTypeVariable('any') }, + getEntityTimeseries: { + description: 'Get entity timeseries', + meta: 'function', + args: [ + {name: 'entityId', type: entityIdHref, description: 'Id of the entity'}, + {name: 'keys', type: `Array<string>`, description: 'Array of the keys'}, + {name: 'startTs', type: 'number', description: 'Start time in milliseconds'}, + {name: 'endTs', type: 'number', description: 'End time in milliseconds'}, + {name: 'limit', type: 'number', description: 'Limit of values to receive for each key'}, + {name: 'agg', type: aggregationTypeHref, description: 'Aggregation type'}, + {name: 'interval', type: 'number', description: 'Aggregation interval'}, + {name: 'orderBy', type: dataSortOrderHref, description: 'Data order by time'}, + {name: 'useStrictDataTypes', type: 'boolean', description: 'If "false" all values will be returned as strings'}, + requestConfigArg + ], + return: observableReturnTypeVariable(timeseriesDataHref) + }, } }, entityService: { diff --git a/ui-ngx/src/app/shared/models/alias.models.ts b/ui-ngx/src/app/shared/models/alias.models.ts index f437931f27..74662cfca2 100644 --- a/ui-ngx/src/app/shared/models/alias.models.ts +++ b/ui-ngx/src/app/shared/models/alias.models.ts @@ -108,6 +108,7 @@ export interface EntitySearchQueryFilter { fetchLastLevelOnly?: boolean; } +// tslint:disable-next-line:no-empty-interface export interface ApiUsageStateFilter { } diff --git a/ui-ngx/src/app/shared/models/audit-log.models.ts b/ui-ngx/src/app/shared/models/audit-log.models.ts index 1b450d5568..46efd740a3 100644 --- a/ui-ngx/src/app/shared/models/audit-log.models.ts +++ b/ui-ngx/src/app/shared/models/audit-log.models.ts @@ -51,7 +51,9 @@ export enum ActionType { LOGOUT = 'LOGOUT', LOCKOUT = 'LOCKOUT', ASSIGNED_FROM_TENANT = 'ASSIGNED_FROM_TENANT', - ASSIGNED_TO_TENANT = 'ASSIGNED_TO_TENANT' + ASSIGNED_TO_TENANT = 'ASSIGNED_TO_TENANT', + PROVISION_SUCCESS = 'PROVISION_SUCCESS', + PROVISION_FAILURE = 'PROVISION_FAILURE' } export enum ActionStatus { @@ -83,7 +85,9 @@ export const actionTypeTranslations = new Map( [ActionType.LOGOUT, 'audit-log.type-logout'], [ActionType.LOCKOUT, 'audit-log.type-lockout'], [ActionType.ASSIGNED_FROM_TENANT, 'audit-log.type-assigned-from-tenant'], - [ActionType.ASSIGNED_TO_TENANT, 'audit-log.type-assigned-to-tenant'] + [ActionType.ASSIGNED_TO_TENANT, 'audit-log.type-assigned-to-tenant'], + [ActionType.PROVISION_SUCCESS, 'audit-log.type-provision-success'], + [ActionType.PROVISION_FAILURE, 'audit-log.type-provision-failure'] ] ); diff --git a/ui-ngx/src/app/shared/models/constants.ts b/ui-ngx/src/app/shared/models/constants.ts index 71dcb16a37..4d6551fb2e 100644 --- a/ui-ngx/src/app/shared/models/constants.ts +++ b/ui-ngx/src/app/shared/models/constants.ts @@ -59,6 +59,7 @@ const helpBaseUrl = 'https://thingsboard.io'; export const HelpLinks = { linksMap: { outgoingMailSettings: helpBaseUrl + '/docs/user-guide/ui/mail-settings', + smsProviderSettings: helpBaseUrl + '/docs/user-guide/ui/sms-provider-settings', securitySettings: helpBaseUrl + '/docs/user-guide/ui/security-settings', oauth2Settings: helpBaseUrl + '/docs/user-guide/oauth-2-support/', ruleEngine: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/overview/', @@ -107,6 +108,7 @@ export const HelpLinks = { ruleNodeRabbitMq: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/external-nodes/#rabbitmq-node', ruleNodeRestApiCall: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/external-nodes/#rest-api-call-node', ruleNodeSendEmail: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/external-nodes/#send-email-node', + ruleNodeSendSms: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/external-nodes/#send-sms-node', tenants: helpBaseUrl + '/docs/user-guide/ui/tenants', tenantProfiles: helpBaseUrl + '/docs/user-guide/ui/tenant-profiles', customers: helpBaseUrl + '/docs/user-guide/ui/customers', @@ -219,5 +221,6 @@ export const contentTypesMap = new Map( ); export const customTranslationsPrefix = 'custom.'; +export const i18nPrefix = 'i18n'; export const MODULES_MAP = new InjectionToken<{[key: string]: any}>('ModulesMap'); diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index e6d96346cd..ffeae2f56d 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -148,6 +148,9 @@ export interface DefaultDeviceProfileTransportConfiguration { export interface MqttDeviceProfileTransportConfiguration { deviceTelemetryTopic?: string; deviceAttributesTopic?: string; + transportPayloadTypeConfiguration?: { + transportPayloadType?: MqttTransportPayloadType; + }; [key: string]: any; } @@ -207,7 +210,7 @@ export function createDeviceProfileTransportConfiguration(type: DeviceTransportT const mqttTransportConfiguration: MqttDeviceProfileTransportConfiguration = { deviceTelemetryTopic: 'v1/devices/me/telemetry', deviceAttributesTopic: 'v1/devices/me/attributes', - transportPayloadType: MqttTransportPayloadType.JSON + transportPayloadTypeConfiguration: {transportPayloadType: MqttTransportPayloadType.JSON} }; transportConfiguration = {...mqttTransportConfiguration, type: DeviceTransportType.MQTT}; break; 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 231a4b82df..df989401d9 100644 --- a/ui-ngx/src/app/shared/models/rule-node.models.ts +++ b/ui-ngx/src/app/shared/models/rule-node.models.ts @@ -418,7 +418,8 @@ const ruleNodeClazzHelpLinkMap = { 'org.thingsboard.rule.engine.mqtt.azure.TbAzureIotHubNode': 'ruleNodeAzureIotHub', 'org.thingsboard.rule.engine.rabbitmq.TbRabbitMqNode': 'ruleNodeRabbitMq', 'org.thingsboard.rule.engine.rest.TbRestApiCallNode': 'ruleNodeRestApiCall', - 'org.thingsboard.rule.engine.mail.TbSendEmailNode': 'ruleNodeSendEmail' + 'org.thingsboard.rule.engine.mail.TbSendEmailNode': 'ruleNodeSendEmail', + 'org.thingsboard.rule.engine.sms.TbSendSmsNode': 'ruleNodeSendSms' }; export function getRuleNodeHelpLink(component: RuleNodeComponentDescriptor): string { diff --git a/ui-ngx/src/app/shared/models/settings.models.ts b/ui-ngx/src/app/shared/models/settings.models.ts index f71ac1bb2e..64c5dda85c 100644 --- a/ui-ngx/src/app/shared/models/settings.models.ts +++ b/ui-ngx/src/app/shared/models/settings.models.ts @@ -14,6 +14,9 @@ /// limitations under the License. /// +import { ValidatorFn } from '@angular/forms'; +import { isNotEmptyStr } from '@core/utils'; + export const smtpPortPattern: RegExp = /^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/; export interface AdminSettings { @@ -60,3 +63,96 @@ export interface UpdateMessage { message: string; updateAvailable: boolean; } + +export const phoneNumberPattern = /^\+[1-9]\d{1,14}$/; + +export enum SmsProviderType { + AWS_SNS = 'AWS_SNS', + TWILIO = 'TWILIO' +} + +export const smsProviderTypeTranslationMap = new Map( + [ + [SmsProviderType.AWS_SNS, 'admin.sms-provider-type-aws-sns'], + [SmsProviderType.TWILIO, 'admin.sms-provider-type-twilio'] + ] +); + +export interface AwsSnsSmsProviderConfiguration { + accessKeyId?: string; + secretAccessKey?: string; + region?: string; +} + +export interface TwilioSmsProviderConfiguration { + accountSid?: string; + accountToken?: string; + numberFrom?: string; +} + +export type SmsProviderConfigurations = AwsSnsSmsProviderConfiguration & TwilioSmsProviderConfiguration; + +export interface SmsProviderConfiguration extends SmsProviderConfigurations { + type: SmsProviderType; +} + +export function smsProviderConfigurationValidator(required: boolean): ValidatorFn { + return control => { + const configuration: SmsProviderConfiguration = control.value; + let errors = null; + if (required) { + let valid = false; + if (configuration && configuration.type) { + switch (configuration.type) { + case SmsProviderType.AWS_SNS: + const awsSnsConfiguration: AwsSnsSmsProviderConfiguration = configuration; + valid = isNotEmptyStr(awsSnsConfiguration.accessKeyId) && isNotEmptyStr(awsSnsConfiguration.secretAccessKey) + && isNotEmptyStr(awsSnsConfiguration.region); + break; + case SmsProviderType.TWILIO: + const twilioConfiguration: TwilioSmsProviderConfiguration = configuration; + valid = isNotEmptyStr(twilioConfiguration.numberFrom) && isNotEmptyStr(twilioConfiguration.accountSid) + && isNotEmptyStr(twilioConfiguration.accountToken); + break; + } + } + if (!valid) { + errors = { + invalid: true + }; + } + } + return errors; + }; +} + +export interface TestSmsRequest { + providerConfiguration: SmsProviderConfiguration; + numberTo: string; + message: string; +} + +export function createSmsProviderConfiguration(type: SmsProviderType): SmsProviderConfiguration { + let smsProviderConfiguration: SmsProviderConfiguration; + if (type) { + switch (type) { + case SmsProviderType.AWS_SNS: + const awsSnsSmsProviderConfiguration: AwsSnsSmsProviderConfiguration = { + accessKeyId: '', + secretAccessKey: '', + region: 'us-east-1' + }; + smsProviderConfiguration = {...awsSnsSmsProviderConfiguration, type: SmsProviderType.AWS_SNS}; + break; + case SmsProviderType.TWILIO: + const twilioSmsProviderConfiguration: TwilioSmsProviderConfiguration = { + numberFrom: '', + accountSid: '', + accountToken: '' + }; + smsProviderConfiguration = {...twilioSmsProviderConfiguration, type: SmsProviderType.TWILIO}; + break; + } + } + return smsProviderConfiguration; +} diff --git a/ui-ngx/src/app/shared/models/tenant.model.ts b/ui-ngx/src/app/shared/models/tenant.model.ts index ad946efdb7..0c82dcba35 100644 --- a/ui-ngx/src/app/shared/models/tenant.model.ts +++ b/ui-ngx/src/app/shared/models/tenant.model.ts @@ -26,6 +26,10 @@ export enum TenantProfileType { export interface DefaultTenantProfileConfiguration { maxDevices: number; maxAssets: number; + maxCustomers: number; + maxUsers: number; + maxDashboards: number; + maxRuleChains: number; transportTenantMsgRateLimit?: string; transportTenantTelemetryMsgRateLimit?: string; @@ -40,6 +44,10 @@ export interface DefaultTenantProfileConfiguration { maxJSExecutions: number; maxDPStorageDays: number; maxRuleNodeExecutionsPerMessage: number; + maxEmails: number; + maxSms: number; + + defaultStorageTtlDays: number; } export type TenantProfileConfigurations = DefaultTenantProfileConfiguration; @@ -56,12 +64,19 @@ export function createTenantProfileConfiguration(type: TenantProfileType): Tenan const defaultConfiguration: DefaultTenantProfileConfiguration = { maxDevices: 0, maxAssets: 0, + maxCustomers: 0, + maxUsers: 0, + maxDashboards: 0, + maxRuleChains: 0, maxTransportMessages: 0, maxTransportDataPoints: 0, maxREExecutions: 0, maxJSExecutions: 0, maxDPStorageDays: 0, - maxRuleNodeExecutionsPerMessage: 0 + maxRuleNodeExecutionsPerMessage: 0, + maxEmails: 0, + maxSms: 0, + defaultStorageTtlDays: 0 }; configuration = {...defaultConfiguration, type: TenantProfileType.DEFAULT}; break; diff --git a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json index e1b06f543b..8483fb1113 100644 --- a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json +++ b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json @@ -1937,25 +1937,6 @@ } }, "language": { - "language": "Jazyk", - "locales": { - "de_DE": "Deutsch", - "fr_FR": "Français", - "zh_CN": "简体中文", - "zh_TW": "繁體中文", - "en_US": "English", - "it_IT": "Italiano", - "ko_KR": "한글", - "ru_RU": "Русский", - "es_ES": "Español", - "ja_JA": "日本語", - "tr_TR": "Türkçe", - "fa_IR": "فارسي", - "uk_UA": "Українська", - "cs_CZ": "Česky", - "el_GR": "Ελληνικά", - "ro_RO": "Română", - "lv_LV": "Latviešu" - } + "language": "Jazyk" } } 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 76799b73fd..14ed8a3f52 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -104,6 +104,33 @@ "proxy-user": "Proxy user", "proxy-password": "Proxy password", "send-test-mail": "Send test mail", + "sms-provider": "SMS provider", + "sms-provider-settings": "SMS provider settings", + "sms-provider-type": "SMS provider type", + "sms-provider-type-required": "SMS provider type is required.", + "sms-provider-type-aws-sns": "Amazon SNS", + "sms-provider-type-twilio": "Twilio", + "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", + "number-from": "Phone Number From", + "number-from-required": "Phone Number From is required.", + "number-to": "Phone Number To", + "number-to-required": "Phone Number To is required.", + "phone-number-hint": "Phone Number in E.164 format, ex. +19995550123", + "phone-number-pattern": "Invalid phone number. Should be in E.164 format, ex. +19995550123.", + "sms-message": "SMS message", + "sms-message-required": "SMS message is required.", + "sms-message-max-length": "SMS message can't be longer 1600 characters", + "twilio-account-sid": "Twilio Account SID", + "twilio-account-sid-required": "Twilio Account SID is required", + "twilio-account-token": "Twilio Account Token", + "twilio-account-token-required": "Twilio Account Token is required", + "send-test-sms": "Send test SMS", + "test-sms-sent": "Test SMS was successfully sent!", "security-settings": "Security settings", "password-policy": "Password policy", "minimum-password-length": "Minimum password length", @@ -405,6 +432,59 @@ "no-attributes-text": "No attributes found", "no-telemetry-text": "No telemetry found" }, + "api-usage": { + "api-usage": "Api Usage", + "data-points": "Data points", + "data-points-storage-days": "Data points storage days", + "email": "Email", + "email-messages": "Email messages", + "email-messages-daily-activity": "Email messages daily activity", + "email-messages-hourly-activity": "Email messages hourly activity", + "email-messages-monthly-activity": "Email messages monthly activity", + "exceptions": "Exceptions", + "executions": "Executions", + "javascript": "JavaScript", + "javascript-executions": "JavaScript executions", + "javascript-functions": "JavaScript functions", + "javascript-functions-daily-activity": "JavaScript functions daily activity", + "javascript-functions-hourly-activity": "JavaScript functions hourly activity", + "javascript-functions-monthly-activity": "JavaScript functions monthly activity", + "latest-error": "Latest Error", + "messages": "Messages", + "permanent-failures": "${entityName} Permanent Failures", + "permanent-timeouts": "${entityName} Permanent Timeouts", + "processing-failures": "${entityName} Processing Failures", + "processing-failures-and-timeouts": "Processing Failures and Timeouts", + "processing-timeouts": "${entityName} Processing Timeouts", + "queue-stats": "Queue Stats", + "rule-chain": "Rule Chain", + "rule-engine": "Rule Engine", + "rule-engine-daily-activity": "Rule Engine daily activity", + "rule-engine-executions": "Rule Engine executions", + "rule-engine-hourly-activity": "Rule Engine hourly activity", + "rule-engine-monthly-activity": "Rule Engine monthly activity", + "rule-engine-statistics": "Rule Engine Statistics", + "rule-node": "Rule Node", + "sms": "SMS", + "sms-messages": "SMS messages", + "sms-messages-daily-activity": "SMS messages daily activity", + "sms-messages-hourly-activity": "SMS messages hourly activity", + "sms-messages-monthly-activity": "SMS messages monthly activity", + "successful": "${entityName} Successful", + "telemetry": "Telemetry", + "telemetry-persistence": "Telemetry persistence", + "telemetry-persistence-daily-activity": "Telemetry persistence daily activity", + "telemetry-persistence-hourly-activity": "Telemetry persistence hourly activity", + "telemetry-persistence-monthly-activity": "Telemetry persistence monthly activity", + "transport": "Transport", + "transport-daily-activity": "Transport daily activity", + "transport-data-points": "Transport data points", + "transport-hourly-activity": "Transport hourly activity", + "transport-messages": "Transport messages", + "transport-monthly-activity": "Transport monthly activity", + "view-details": "View details", + "view-statistics": "View statistics" + }, "audit-log": { "audit": "Audit", "audit-logs": "Audit Logs", @@ -445,7 +525,9 @@ "search": "Search audit logs", "clear-search": "Clear search", "type-assigned-from-tenant": "Assigned from Tenant", - "type-assigned-to-tenant": "Assigned to Tenant" + "type-assigned-to-tenant": "Assigned to Tenant", + "type-provision-success": "Device provisioned", + "type-provision-failure": "Device provisioning was failed" }, "confirm-on-exit": { "message": "You have unsaved changes. Are you sure you want to leave this page?", @@ -814,8 +896,10 @@ "details": "Details", "copyId": "Copy device Id", "copyAccessToken": "Copy access token", + "copy-mqtt-authentication": "Copy MQTT credentials", "idCopiedMessage": "Device Id has been copied to clipboard", "accessTokenCopiedMessage": "Device access token has been copied to clipboard", + "mqtt-authentication-copied-message": "Device MQTT authentication has been copied to clipboard", "assignedToCustomer": "Assigned to customer", "unable-delete-device-alias-title": "Unable to delete device alias", "unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):
{{widgetsList}}", @@ -895,6 +979,10 @@ "telemetry-topic-filter-required": "Telemetry topic filter is required.", "attributes-topic-filter": "Attributes topic filter", "attributes-topic-filter-required": "Attributes topic filter is required.", + "telemetry-proto-schema": "Telemetry proto schema", + "telemetry-proto-schema-required": "Telemetry proto schema is required.", + "attributes-proto-schema": "Attributes proto schema", + "attributes-proto-schema-required": "Attributes proto schema is required.", "rpc-response-topic-filter": "RPC response topic filter", "rpc-response-topic-filter-required": "RPC response topic filter is required.", "not-valid-pattern-topic-filter": "Not valid pattern topic filter", @@ -909,7 +997,7 @@ "edit-alarm-rule": "Edit alarm rule", "alarm-type": "Alarm type", "alarm-type-required": "Alarm type is required.", - "alarm-type-pattern-hint": "Alarm type pattern, use ${metaKeyName} to substitute variables from metadata", + "alarm-type-unique": "Alarm type must be unique within the device profile alarm rules.", "create-alarm-pattern": "Create {{alarmType}} alarm", "create-alarm-rules": "Create alarm rules", "no-create-alarm-rules": "No create conditions configured", @@ -941,12 +1029,16 @@ "provision-strategy": "Provision strategy", "provision-strategy-required": "Provision strategy is required.", "provision-strategy-disabled": "Disabled", - "provision-strategy-created-new": "Allow create new devices", - "provision-strategy-check-pre-provisioned": "Check pre provisioned devices", + "provision-strategy-created-new": "Allow to create new devices", + "provision-strategy-check-pre-provisioned": "Check for pre-provisioned devices", "provision-device-key": "Provision device key", "provision-device-key-required": "Provision device key is required.", + "copy-provision-key": "Copy provision key", + "provision-key-copied-message": "Provision key has been copied to clipboard", "provision-device-secret": "Provision device secret", "provision-device-secret-required": "Provision device secret is required.", + "copy-provision-secret": "Copy provision secret", + "provision-secret-copied-message": "Provision secret has been copied to clipboard", "condition": "Condition", "condition-type": "Condition type", "condition-type-simple": "Simple", @@ -1946,6 +2038,18 @@ "maximum-assets": "Maximum number of assets (0 - unlimited)", "maximum-assets-required": "Maximum number of assets is required.", "maximum-assets-range": "Maximum number of assets can't be negative", + "maximum-customers": "Maximum number of customers (0 - unlimited)", + "maximum-customers-required": "Maximum number of customers is required.", + "maximum-customers-range": "Maximum number of customers can't be negative", + "maximum-users": "Maximum number of users (0 - unlimited)", + "maximum-users-required": "Maximum number of users is required.", + "maximum-users-range": "Maximum number of users can't be negative", + "maximum-dashboards": "Maximum number of dashboards (0 - unlimited)", + "maximum-dashboards-required": "Maximum number of dashboards is required.", + "maximum-dashboards-range": "Maximum number of dashboards can't be negative", + "maximum-rule-chains": "Maximum number of rule chains (0 - unlimited)", + "maximum-rule-chains-required": "Maximum number of rule chains is required.", + "maximum-rule-chains-range": "Maximum number of rule chains can't be negative", "transport-tenant-msg-rate-limit": "Transport tenant messages rate limit.", "transport-tenant-telemetry-msg-rate-limit": "Transport tenant telemetry messages rate limit.", "transport-tenant-telemetry-data-points-rate-limit": "Transport tenant telemetry data points rate limit.", @@ -1957,19 +2061,28 @@ "max-transport-messages-range": "Maximum number of transport messages can't be negative", "max-transport-data-points": "Maximum number of transport data points (0 - unlimited)", "max-transport-data-points-required": "Maximum number of transport data points is required.", - "max-transport-data-points-range": "Minimum number of transport data points can't be negative", + "max-transport-data-points-range": "Maximum number of transport data points can't be negative", "max-r-e-executions": "Maximum number of Rule Engine executions (0 - unlimited)", "max-r-e-executions-required": "Maximum number of Rule Engine executions is required.", - "max-r-e-executions-range": "Minimum number of Rule Engine executions can't be negative", + "max-r-e-executions-range": "Maximum number of Rule Engine executions can't be negative", "max-j-s-executions": "Maximum number of JavaScript executions (0 - unlimited)", "max-j-s-executions-required": "Maximum number of JavaScript executions is required.", - "max-j-s-executions-range": "Minimum number of JavaScript executions can't be negative", + "max-j-s-executions-range": "Maximum number of JavaScript executions can't be negative", "max-d-p-storage-days": "Maximum number of data points storage days (0 - unlimited)", "max-d-p-storage-days-required": "Maximum number of data points storage days is required.", - "max-d-p-storage-days-range": "Minimum number of data points storage days can't be negative", + "max-d-p-storage-days-range": "Maximum number of data points storage days can't be negative", + "default-storage-ttl-days": "Default storage TTL days (0 - unlimited)", + "default-storage-ttl-days-required": "Default storage TTL days is required.", + "default-storage-ttl-days-range": "Default storage TTL days can't be negative", "max-rule-node-executions-per-message": "Maximum number of rule node executions per message (0 - unlimited)", "max-rule-node-executions-per-message-required": "Maximum number of rule node executions per message is required.", - "max-rule-node-executions-per-message-range": "Minimum number of rule node executions per message can't be negative" + "max-rule-node-executions-per-message-range": "Maximum number of rule node executions per message can't be negative", + "max-emails": "Maximum number of emails sent (0 - unlimited)", + "max-emails-required": "Maximum number of emails sent is required.", + "max-emails-range": "Maximum number of emails sent can't be negative", + "max-sms": "Maximum number of SMS sent (0 - unlimited)", + "max-sms-required": "Maximum number of SMS sent is required.", + "max-sms-range": "Maximum number of SMS sent can't be negative" }, "timeinterval": { "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }", @@ -2381,7 +2494,8 @@ "ro_RO": "Română", "lv_LV": "Latviešu", "ka_GE": "ქართული", - "pt_BR": "Português do Brasil" + "pt_BR": "Português do Brasil", + "sl_SI": "Slovenščina" } } } diff --git a/ui-ngx/src/assets/locale/locale.constant-ko_KR.json b/ui-ngx/src/assets/locale/locale.constant-ko_KR.json index c255440ba7..66090425b1 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ko_KR.json +++ b/ui-ngx/src/assets/locale/locale.constant-ko_KR.json @@ -266,7 +266,7 @@ "add-to-dashboard": "대시보드에 추가", "add-widget-to-dashboard": "대시보드에 위젯 추가", "selected-attributes": "{ count, plural, 1 {속성 1개} other {속성 #개} } 선택됨", - "selected-telemetry": "{ count, plural, 1 {최근 데이터 1개} other {최근 데이터 #개} } 선택됨" + "selected-telemetry": "{ count, plural, 1 {최근 데이터 1개} other {최근 데이터 #개} } 선택됨", "no-attributes-text": "아무 속성도 찾을 수 없습니다", "no-telemetry-text": "아무 텔레메트리도 찾을 수 없습니다." }, diff --git a/ui-ngx/src/assets/locale/locale.constant-sl_SI.json b/ui-ngx/src/assets/locale/locale.constant-sl_SI.json new file mode 100644 index 0000000000..1b3077a875 --- /dev/null +++ b/ui-ngx/src/assets/locale/locale.constant-sl_SI.json @@ -0,0 +1,2479 @@ +{ + "access": { + "unauthorized": "Nepooblaščeno", + "unauthorized-access": "Nepooblaščen dostop", + "unauthorized-access-text": "Za dostop do tega vira se morate prijaviti!", + "access-forbidden": "Dostop prepovedan", + "access-forbidden-text": "Nimate pravic dostopa do te lokacije!
Poskusite se prijaviti z drugim uporabnikom, če še vedno želite dostopati do te lokacije.", + "refresh-token-expired": "Seja je potekla", + "refresh-token-failed": "Seje ni mogoče osvežiti", + "permission-denied": "Dovoljenje zavrnjeno", + "permission-denied-text": "Nimate dovoljenja za izvedbo tega dejanja!" + }, + "action": { + "activate": "Aktiviraj", + "suspend": "Začasno ustavi", + "save": "Shrani", + "saveAs": "Shrani kot", + "cancel": "Prekliči", + "ok": "V redu", + "delete": "Izbriši", + "add": "Dodaj", + "yes": "Da", + "no": "Ne", + "update": "Nadgradnja", + "remove": "Odstrani", + "select": "Izberi", + "search": "Iskanje", + "clear-search": "Počisti iskanje", + "assign": "Dodeli", + "unassign": "Preklic dodelitve", + "share": "Deliti", + "make-private": "Naj bo zasebno", + "apply": "Uporabi", + "apply-changes": "Uporabi spremembe", + "edit-mode": "Način urejanja", + "enter-edit-mode": "Vstopi v način urejanja", + "decline-changes": "Zavrni spremembe", + "close": "Zapri", + "back": "Nazaj", + "run": "Zaženi", + "sign-in": "Prijavite se!", + "edit": "Uredi", + "view": "Pogled", + "create": "Ustvari", + "drag": "Povleci", + "refresh": "Osveži", + "undo": "Razveljavi", + "copy": "Kopiraj", + "paste": "Prilepi", + "copy-reference": "Kopiraj sklic", + "paste-reference": "Prilepi sklic", + "import": "Uvozi", + "export": "Izvozi", + "share-via": "Deli prek {{provider}}", + "continue": "Nadaljuj", + "discard-changes": "Zavrzi spremembe", + "download": "Prenesi", + "next-with-label": "Naslednji: {{label}}", + "read-more": "Preberi več", + "hide": "Skrij" + }, + "aggregation": { + "aggregation": "Združevanje", + "function": "Funkcija združevanja podatkov", + "limit": "Največje vrednosti", + "group-interval": "Interval združevanja", + "min": "Min", + "max": "Max", + "avg": "Povprečje", + "sum": "Vsota", + "count": "Štetje", + "none": "Brez" + }, + "admin": { + "general": "Splošno", + "general-settings": "Splošne nastavitve", + "outgoing-mail": "Poštni strežnik", + "outgoing-mail-settings": "Nastavitve strežnika odhodne pošte", + "system-settings": "Sistemske nastavitve", + "test-mail-sent": "Testna pošta je bila uspešno poslana!", + "base-url": "Osnovni URL", + "base-url-required": "Zahtevan je osnovni URL.", + "prohibit-different-url": "Prohibit to use hostname from the client request headers", + "prohibit-different-url-hint": "This setting should be enabled for production environments. May cause security issues when disabled", + "mail-from": "Pošta od", + "mail-from-required": "Zahtevana je pošta od.", + "smtp-protocol": "Protokol SMTP", + "smtp-host": "SMTP gostitelj", + "smtp-host-required": "Zahtevan je gostitelj SMTP.", + "smtp-port": "Vrata SMTP", + "smtp-port-required": "Navesti morate vrata SMTP.", + "smtp-port-invalid": "To ne izgleda kot veljavna vrata SMTP.", + "timeout-msec": "Časovna omejitev (msec)", + "timeout-required": "Časovna omejitev je potrebna.", + "timeout-invalid": "To ne izgleda kot veljavna časovna omejitev.", + "enable-tls": "Omogoči TLS", + "tls-version": "Različica TLS", + "enable-proxy": "Omogoči proxy", + "proxy-host": "Gostitelj proxy", + "proxy-host-required": "Zahtevan je strežnik proxy.", + "proxy-port": "Proxy vrata", + "proxy-port-required": "Vrata proxy so potrebna.", + "proxy-port-range": "Vrata proxy naj bodo v območju od 1 do 65535.", + "proxy-user": "Uporabnik proxy", + "proxy-password": "Geslo proxy", + "send-test-mail": "Pošlji testno pošto", + "sms-provider": "SMS provider", + "sms-provider-settings": "SMS provider settings", + "sms-provider-type": "SMS provider type", + "sms-provider-type-required": "SMS provider type is required.", + "sms-provider-type-aws-sns": "Amazon SNS", + "sms-provider-type-twilio": "Twilio", + "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", + "number-from": "Phone Number From", + "number-from-required": "Phone Number From is required.", + "number-to": "Phone Number To", + "number-to-required": "Phone Number To is required.", + "phone-number-hint": "Phone Number in E.164 format, ex. +19995550123", + "phone-number-pattern": "Invalid phone number. Should be in E.164 format, ex. +19995550123.", + "sms-message": "SMS message", + "sms-message-required": "SMS message is required.", + "sms-message-max-length": "SMS message can't be longer 1600 characters", + "twilio-account-sid": "Twilio Account SID", + "twilio-account-sid-required": "Twilio Account SID is required", + "twilio-account-token": "Twilio Account Token", + "twilio-account-token-required": "Twilio Account Token is required", + "send-test-sms": "Send test SMS", + "test-sms-sent": "Test SMS was successfully sent!", + "security-settings": "Varnostne nastavitve", + "password-policy": "Pravilnik o geslih", + "minimum-password-length": "Najkrajša dolžina gesla", + "minimum-password-length-required": "Zahtevana je najkrajša dolžina gesla", + "minimum-password-length-range": "Minimalna dolžina gesla naj bo v območju od 5 do 50", + "minimum-uppercase-letters": "Najmanjše število velikih črk", + "minimum-uppercase-letters-range": "Najmanjše število velikih črk ne sme biti negativno", + "minimum-lowercase-letters": "Najmanjše število malih črk", + "minimum-lowercase-letters-range": "Najmanjše število malih črk ne sme biti negativno", + "minimum-digits": "Najmanjše število številk", + "minimum-digits-range": "Najmanjše število številk ne sme biti negativno", + "minimum-special-characters": "Najmanjše število posebnih znakov", + "minimum-special-characters-range": "Najmanjše število posebnih znakov ne sme biti negativno", + "password-expiration-period-days": "Obdobje veljavnosti gesla v dneh", + "password-expiration-period-days-range": "Obdobje veljavnosti gesla v dneh ne sme biti negativno", + "password-reuse-frequency-days": "Pogostost ponovne uporabe gesla v dneh", + "password-reuse-frequency-days-range": "Pogostost ponovne uporabe gesla v dneh ne sme biti negativna", + "general-policy": "Splošna politika", + "max-failed-login-attempts": "Največje število neuspelih poskusov prijave, preden se račun zaklene", + "minimum-max-failed-login-attempts-range": "Največje število neuspelih poskusov prijave ne sme biti negativno", + "user-lockout-notification-email": "V primeru zaklepa uporabniškega računa, pošlji obvestilo na e-pošto", + "domain-name": "Domain name", + "domain-name-unique": "Domain name and protocol need to unique.", + "error-verification-url": "A domain name shouldn't contain symbols '/' and ':'. Example: thingsboard.io", + "oauth2": { + "access-token-uri": "Access token URI", + "access-token-uri-required": "Access token URI is required.", + "activate-user": "Activate user", + "add-domain": "Add domain", + "delete-domain": "Delete domain", + "add-provider": "Add provider", + "delete-provider": "Delete provider", + "allow-user-creation": "Allow user creation", + "always-fullscreen": "Always fullscreen", + "authorization-uri": "Authorization URI", + "authorization-uri-required": "Authorization URI is required.", + "client-authentication-method": "Client authentication method", + "client-id": "Client ID", + "client-id-required": "Client ID is required.", + "client-secret": "Client secret", + "client-secret-required": "Client secret is required.", + "custom-setting": "Custom settings", + "customer-name-pattern": "Customer name pattern", + "default-dashboard-name": "Default dashboard name", + "delete-domain-text": "Be careful, after the confirmation a domain and all provider data will be unavailable.", + "delete-domain-title": "Are you sure you want to delete settings the domain '{{domainName}}'?", + "delete-registration-text": "Be careful, after the confirmation a provider data will be unavailable.", + "delete-registration-title": "Are you sure you want to delete the provider '{{name}}'?", + "email-attribute-key": "Email attribute key", + "email-attribute-key-required": "Email attribute key is required.", + "first-name-attribute-key": "First name attribute key", + "general": "General", + "jwk-set-uri": "JSON Web Key URI", + "last-name-attribute-key": "Last name attribute key", + "login-button-icon": "Login button icon", + "login-button-label": "Provider label", + "login-button-label-placeholder": "Login with $(Provider label)", + "login-button-label-required": "Label is required.", + "login-provider": "Login provider", + "mapper": "Mapper", + "new-domain": "New domain", + "oauth2": "OAuth2", + "redirect-uri-template": "Redirect URI template", + "copy-redirect-uri": "Copy redirect URI", + "registration-id": "Registration ID", + "registration-id-required": "Registration ID is required.", + "registration-id-unique": "Registration ID need to unique for the system.", + "scope": "Scope", + "scope-required": "Scope is required.", + "tenant-name-pattern": "Tenant name pattern", + "tenant-name-pattern-required": "Tenant name pattern is required.", + "tenant-name-strategy": "Tenant name strategy", + "type": "Mapper type", + "uri-pattern-error": "Invalid URI format.", + "url": "URL", + "url-pattern": "Invalid URL format.", + "url-required": "URL is required.", + "user-info-uri": "User info URI", + "user-info-uri-required": "User info URI is required.", + "user-name-attribute-name": "User name attribute key", + "user-name-attribute-name-required": "User name attribute key is required", + "protocol": "Protocol", + "domain-schema-http": "HTTP", + "domain-schema-https": "HTTPS", + "domain-schema-mixed": "HTTP+HTTPS", + "enable": "Enable OAuth2 settings" + } + }, + "alarm": { + "alarm": "Alarm", + "alarms": "Alarmi", + "select-alarm": "Izberi alarm", + "no-alarms-matching": "Najdeni niso bili nobeni alarmi, ki se ujemajo z '{{entity}}'.", + "alarm-required": "Potreben je alarm", + "alarm-status": "Stanje alarma", + "alarm-status-list": "Seznam stanja alarma", + "any-status": "Kateri koli status", + "search-status": { + "ANY": "Katerikoli", + "ACTIVE": "Aktivno", + "CLEARED": "Počiščeno", + "ACK": "Potrjeno", + "UNACK": "Nepriznano" + }, + "display-status": { + "ACTIVE_UNACK": "Aktivno nepriznano", + "ACTIVE_ACK": "Aktivno potrjeno", + "CLEARED_UNACK": "Počiščeno nepriznano", + "CLEARED_ACK": "Počiščeno potrjeno" + }, + "no-alarms-prompt": "Ni najdenih alarmov", + "created-time": "Ustvarjeni čas", + "type": "Vrsta", + "severity": "Resnost", + "originator": "Izvirnik", + "originator-type": "Vrsta izvirnika", + "details": "Podrobnosti", + "status": "Stanje", + "alarm-details": "Podrobnosti alarma", + "start-time": "Začetni čas", + "end-time": "Končni čas", + "ack-time": "Priznani čas", + "clear-time": "Očiščen čas", + "alarm-severity-list": "Seznam resnosti alarma", + "any-severity": "Katerakoli resnost", + "severity-critical": "Kritična", + "severity-major": "Visoka", + "severity-minor": "Nizka", + "severity-warning": "Opozorilo", + "severity-indeterminate": "Nedoločeno", + "acknowledge": "Potrdi", + "clear": "Počisti", + "search": "Iskanje alarmov", + "selected-alarms": "{ count, plural, 1 {1 alarm} other {# alarms} } izbran", + "no-data": "Ni podatkov za prikaz", + "polling-interval": "Interval iskanja alarmov (s)", + "polling-interval-required": "Zahtevan je interval glasovanja alarmov.", + "min-polling-interval-message": "Dovoljen je vsaj 1-sekundni interval glasovanja.", + "aknowledge-alarms-title": "Potrdite { count, plural, 1 {1 alarm} other {# alarms} }", + "aknowledge-alarms-text": "Ali ste prepričani, da želite potrditi { count, plural, 1 {1 alarm} other {# alarms} }?", + "aknowledge-alarm-title": "Potrdite alarm", + "aknowledge-alarm-text": "Ali ste prepričani, da želite potrditi alarm?", + "clear-alarms-title": "Počisti { count, plural, 1 {1 alarm} other {# alarmi} }", + "clear-alarms-text": "Ali ste prepričani, da želite počistiti { count, plural, 1 {1 alarm} other {# alarme} }?", + "clear-alarm-title": "Počisti alarm", + "clear-alarm-text": "Ali ste prepričani, da želite počistiti alarm?", + "alarm-status-filter": "Filter stanja alarma", + "alarm-filter": "Alarmni filter", + "max-count-load": "Največje število naloženih alarmov (0 - neomejeno)", + "max-count-load-required": "Zahtevano je največje število alarmov za nalaganje.", + "max-count-load-error-min": "Najmanjša vrednost je 0.", + "fetch-size": "Velikost prenosa", + "fetch-size-required": "Zahtevana je velikost prenosa.", + "fetch-size-error-min": "Najmanjša vrednost je 10.", + "alarm-type-list": "Seznam vrst alarmov", + "any-type": "Katerakoli vrsta", + "search-propagated-alarms": "Iskanje razširjenih alarmov" + }, + "alias": { + "add": "Dodaj vzdevek", + "edit": "Uredi vzdevek", + "name": "Ime vzdevka", + "name-required": "Ime vzdevka je obvezno", + "duplicate-alias": "Vzdevek z istim imenom že obstaja.", + "filter-type-single-entity": "Enota", + "filter-type-entity-list": "Seznam entitet", + "filter-type-entity-name": "Ime entitete", + "filter-type-state-entity": "Subjekt iz stanja nadzorne plošče", + "filter-type-state-entity-description": "Entiteta, vzeta iz parametrov stanja nadzorne plošče", + "filter-type-asset-type": "Vrsta sredstva", + "filter-type-asset-type-description": "Sredstva vrste '{{assetType}}'", + "filter-type-asset-type-and-name-description": "Sredstva vrste '{{assetType}}' in z imenom, ki se začne z '{{prefix}}'", + "filter-type-device-type": "Vrsta naprave", + "filter-type-device-type-description": "Naprave tipa '{{deviceType}}'", + "filter-type-device-type-and-name-description": "Naprave vrste '{{deviceType}}' in z imenom, ki se začne z '{{prefix}}'", + "filter-type-entity-view-type": "Vrsta pogleda entitete", + "filter-type-entity-view-type-description": "Pogledi entitet vrste '{{entityView}}'", + "filter-type-entity-view-type-and-name-description": "Pogledi entitet vrste '{{entityView}}' in z imenom, ki se začne z '{{prefix}}'", + "filter-type-relations-query": "Poizvedba odnosov", + "filter-type-relations-query-description": "{{entities}}, ki imajo {{relationType}} relacijo {{direction}} {{rootEntity}}", + "filter-type-asset-search-query": "Iskalna poizvedba sredstva", + "filter-type-asset-search-query-description": "Sredstva s tipi {{assetTypes}}, ki imajo {{relationType}} relacijo {{direction}} {{rootEntity}}", + "filter-type-device-search-query": "Iskalna poizvedba naprave", + "filter-type-device-search-query-description": "Naprave s tipi {{deviceTypes}}, ki imajo {{relationType}} relacijo {{direction}} {{rootEntity}}", + "filter-type-entity-view-search-query": "Iskalna poizvedba pogleda entitete", + "filter-type-entity-view-search-query-description": "Pogledi entitet s tipi {{entityViewTypes}}, ki imajo {{relationType}} relacijo {{direction}} {{rootEntity}}", + "filter-type-apiUsageState": "Api Usage State", + "entity-filter": "Filter entitet", + "resolve-multiple": "Reši kot več entitet", + "filter-type": "Vrsta filtra", + "filter-type-required": "Zahtevana je vrsta filtra.", + "entity-filter-no-entity-matched": "Najdenih ni nobenih entitet, ki se ujemajo z navedenim filtrom.", + "no-entity-filter-specified": "Ni določen noben filter entitete", + "root-state-entity": "Uporabi entiteto stanja na nadzorni plošči kot izvorno", + "last-level-relation": "Pridobi samo razmerje na zadnji ravni", + "root-entity": "Izvorna entiteta", + "state-entity-parameter-name": "Ime parametra državne entitete", + "default-state-entity": "Privzeta državna entiteta", + "default-entity-parameter-name": "Privzeto", + "max-relation-level": "Najvišja stopnja relacije", + "unlimited-level": "Neomejena raven", + "state-entity": "Entiteta stanja nadzorne plošče", + "all-entities": "Vsi subjekti", + "any-relation": "katerikoli" + }, + "asset": { + "asset": "Sredstvo", + "assets": "Sredstva", + "management": "Upravljanje sredstev", + "view-assets": "Ogled sredstev", + "add": "Dodaj sredstvo", + "assign-to-customer": "Dodeli stranki", + "assign-asset-to-customer": "Dodeli sredstva stranki", + "assign-asset-to-customer-text": "Izberite sredstva, ki jih želite dodeliti stranki", + "no-assets-text": "Sredstva ni bilo mogoče najti", + "assign-to-customer-text": "Izberite stranko, ki bo dodelila sredstvo (-a)", + "public": "Javno", + "assignedToCustomer": "Dodeljeno stranki", + "make-public": "Naj bo sredstvo javno", + "make-private": "Naj bo sredstvo zasebno", + "unassign-from-customer": "Odjavi od stranke", + "delete": "Izbriši sredstvo", + "asset-public": "Sredstvo je javno", + "asset-type": "Vrsta sredstva", + "asset-type-required": "Zahtevana je vrsta sredstva.", + "select-asset-type": "Izberite vrsto sredstva", + "enter-asset-type": "Vnesite vrsto sredstva", + "any-asset": "Katerokoli sredstvo", + "no-asset-types-matching": "Najdena ni bila nobena vrsta sredstva, ki se ujema z '{{entitySubtype}}'.", + "asset-type-list-empty": "Izbrana ni nobena vrsta sredstva.", + "asset-types": "Vrste sredstev", + "name": "Ime", + "name-required": "Ime je obvezno.", + "description": "Opis", + "type": "Vrsta", + "type-required": "Tip je obvezen.", + "details": "Podrobnosti", + "events": "Dogodki", + "add-asset-text": "Dodaj novo sredstvo", + "asset-details": "Podrobnosti o sredstvih", + "assign-assets": "Dodelitev sredstev", + "assign-assets-text": "Stranki dodeli { count, plural, 1 {1 sredstvo} other {# assets} }", + "delete-assets": "Izbriši sredstva", + "unassign-assets": "Preklic dodelitve sredstev", + "unassign-assets-action-title": "Stranki ne dodeli { count, plural, 1 {1 sredstvo} other {# assets} }", + "assign-new-asset": "Dodeli novo sredstvo", + "delete-asset-title": "Ali ste prepričani, da želite izbrisati sredstvo '{{assetName}}'?", + "delete-asset-text": "Bodite previdni, po potrditvi bo sredstvo in vsi povezani podatki nepopravljivi.", + "delete-assets-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 sredstvo} other {# assets} }?", + "delete-assets-action-title": "Izbriši { count, plural, 1 {1 sredstvo} other {# assets} }", + "delete-assets-text": "Bodite previdni, po potrditvi bodo vsa izbrana sredstva odstranjena in vsi povezani podatki bodo postali nepopravljivi.", + "make-public-asset-title": "Ali ste prepričani, da želite sredstvo '{{assetName}}' objaviti kot javno?", + "make-public-asset-text": "Po potrditvi bodo sredstvo in vsi njegovi podatki javno dostopni in dostopni drugim.", + "make-private-asset-title": "Ali ste prepričani, da želite sredstvo '{{assetName}}' narediti zasebno?", + "make-private-asset-text": "Po potrditvi bodo sredstvo in vsi njegovi podatki postali zasebni in ne bodo dostopni drugim.", + "unassign-asset-title": "Ali ste prepričani, da želite dodeliti sredstvo '{{assetName}}'?", + "unassign-asset-text": "Po potrditvi sredstvo ne bo dodeljeno in stranka ne bo dostopna.", + "unassign-asset": "Preklic dodelitve sredstva", + "unassign-assets-title": "Ali ste prepričani, da želite odpovedati { count, plural, 1 {1 asset} other {# assets} }?", + "unassign-assets-text": "Po potrditvi vsa izbrana sredstva ne bodo dodeljena in stranka ne bo dostopna do njih.", + "copyId": "Kopiraj ID sredstva", + "idCopiedMessage": "ID sredstva je kopiran v odložišče", + "select-asset": "Izberi sredstvo", + "no-assets-matching": "Nobeno sredstvo, ki se ujema z '{{entity}}', ni bilo mogoče najti.", + "asset-required": "Zahtevano je sredstvo", + "name-starts-with": "Ime sredstva se začne z", + "import": "Uvozi sredstva", + "asset-file": "Datoteka sredstva", + "search": "Išči sredstva", + "selected-assets": "{ count, plural, 1 {1 asset} other {# assets} } izbrano", + "label": "Oznaka" + }, + "attribute": { + "attributes": "Lastnosti", + "latest-telemetry": "Najnovejša telemetrija", + "attributes-scope": "Obseg atributov entitete", + "scope-latest-telemetry": "Najnovejša telemetrija", + "scope-client": "Atributi odjemalca", + "scope-server": "Atributi strežnika", + "scope-shared": "Skupni atributi", + "add": "Dodaj atribut", + "key": "Ključ", + "last-update-time": "Čas zadnje posodobitve", + "key-required": "Potreben je ključ atributa.", + "value": "Vrednost", + "value-required": "Vrednost atributa je obvezna.", + "delete-attributes-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 attribute} other {# attributes} }?", + "delete-attributes-text": "Bodite previdni, po potrditvi bodo odstranjeni vsi izbrani atributi.", + "delete-attributes": "Izbriši atribute", + "enter-attribute-value": "Vnesite vrednost atributa", + "show-on-widget": "Pokaži na pripomočku", + "widget-mode": "Način pripomočka", + "next-widget": "Naslednji pripomoček", + "prev-widget": "Prejšnji pripomoček", + "add-to-dashboard": "Dodaj na nadzorno ploščo", + "add-widget-to-dashboard": "Dodaj pripomoček na nadzorno ploščo", + "selected-attributes": "{ count, plural, 1 {1 attribute} other {# attributes} } izbrano", + "selected-telemetry": "{ count, plural, 1 {1 telemetry unit} other {# telemetry units} } izbran", + "no-attributes-text": "Ni najdenih atributov", + "no-telemetry-text": "Telemetrija ni najdena" + }, + "api-usage": { + "api-usage": "Api Usage", + "data-points": "Data points", + "data-points-storage-days": "Data points storage days", + "email": "Email", + "email-messages": "Email messages", + "email-messages-daily-activity": "Email messages daily activity", + "email-messages-hourly-activity": "Email messages hourly activity", + "email-messages-monthly-activity": "Email messages monthly activity", + "exceptions": "Exceptions", + "executions": "Executions", + "javascript": "JavaScript", + "javascript-executions": "JavaScript executions", + "javascript-functions": "JavaScript functions", + "javascript-functions-daily-activity": "JavaScript functions daily activity", + "javascript-functions-hourly-activity": "JavaScript functions hourly activity", + "javascript-functions-monthly-activity": "JavaScript functions monthly activity", + "latest-error": "Latest Error", + "messages": "Messages", + "permanent-failures": "${entityName} Permanent Failures", + "permanent-timeouts": "${entityName} Permanent Timeouts", + "processing-failures": "${entityName} Processing Failures", + "processing-failures-and-timeouts": "Processing Failures and Timeouts", + "processing-timeouts": "${entityName} Processing Timeouts", + "queue-stats": "Queue Stats", + "rule-chain": "Rule Chain", + "rule-engine": "Rule Engine", + "rule-engine-daily-activity": "Rule Engine daily activity", + "rule-engine-executions": "Rule Engine executions", + "rule-engine-hourly-activity": "Rule Engine hourly activity", + "rule-engine-monthly-activity": "Rule Engine monthly activity", + "rule-engine-statistics": "Rule Engine Statistics", + "rule-node": "Rule Node", + "sms": "SMS", + "sms-messages": "SMS messages", + "sms-messages-daily-activity": "SMS messages daily activity", + "sms-messages-hourly-activity": "SMS messages hourly activity", + "sms-messages-monthly-activity": "SMS messages monthly activity", + "successful": "${entityName} Successful", + "telemetry": "Telemetry", + "telemetry-persistence": "Telemetry persistence", + "telemetry-persistence-daily-activity": "Telemetry persistence daily activity", + "telemetry-persistence-hourly-activity": "Telemetry persistence hourly activity", + "telemetry-persistence-monthly-activity": "Telemetry persistence monthly activity", + "transport": "Transport", + "transport-daily-activity": "Transport daily activity", + "transport-data-points": "Transport data points", + "transport-hourly-activity": "Transport hourly activity", + "transport-messages": "Transport messages", + "transport-monthly-activity": "Transport monthly activity", + "view-details": "View details", + "view-statistics": "View statistics" + }, + "audit-log": { + "audit": "Revizija", + "audit-logs": "Revizijski dnevniki", + "timestamp": "Časovni žig", + "entity-type": "Vrsta entitete", + "entity-name": "Ime entitete", + "user": "Uporabnik", + "type": "Vrsta", + "status": "Stanje", + "details": "Podrobnosti", + "type-added": "Dodano", + "type-deleted": "Izbrisano", + "type-updated": "Posodobljeno", + "type-attributes-updated": "Atributi posodobljeni", + "type-attributes-deleted": "Atributi izbrisani", + "type-rpc-call": "Klic RPC", + "type-credentials-updated": "Poverilnice posodobljene", + "type-assigned-to-customer": "Dodeljeno kupcu", + "type-unassigned-from-customer": "Odstop od stranke", + "type-activated": "Aktivirano", + "type-suspended": "Suspendirano", + "type-credentials-read": "Poverilnice prebrane", + "type-attributes-read": "Atributi prebrani", + "type-relation-add-or-update": "Razmerje posodobljeno", + "type-relation-delete": "Razmerje izbrisano", + "type-relations-delete": "Vse povezave izbrisane", + "type-alarm-ack": "Potrjeno", + "type-alarm-clear": "Počiščeno", + "type-login": "Vpiši se", + "type-logout": "Odjava", + "type-lockout": "Izključitev", + "status-success": "Uspeh", + "status-failure": "Neuspeh", + "audit-log-details": "Podrobnosti dnevnika revizije", + "no-audit-logs-prompt": "Ni najdenih dnevnikov", + "action-data": "Podatki o dejanju", + "failure-details": "Podrobnosti o napaki", + "search": "Išči dnevnike revizije", + "clear-search": "Počisti iskanje", + "type-assigned-from-tenant": "Dodeljeno od najemnika", + "type-assigned-to-tenant": "Dodeljeno najemniku", + "type-provision-success": "Device provisioned", + "type-provision-failure": "Device provisioning was failed" + }, + "confirm-on-exit": { + "message": "Imate neshranjene spremembe. Ali ste prepričani, da želite zapustiti to stran?", + "html-message": "Imate neshranjene spremembe.
Ali ste prepričani, da želite zapustiti to stran?", + "title": "Neshranjene spremembe" + }, + "contact": { + "country": "Država", + "city": "Mesto", + "state": "Država / Regija", + "postal-code": "Poštna številka", + "postal-code-invalid": "Neveljavna oblika poštne številke.", + "address": "Naslov", + "address2": "Naslov 2", + "phone": "Telefon", + "email": "E-naslov", + "no-address": "Brez naslova" + }, + "common": { + "username": "Uporabniško ime", + "password": "Geslo", + "enter-username": "Vnesite uporabniško ime", + "enter-password": "Vnesite geslo", + "enter-search": "Vnesite iskanje", + "created-time": "Ustvarjeni čas", + "loading": "Nalaganje..." + }, + "content-type": { + "json": "Json", + "text": "Besedilo", + "binary": "Binarni (Base64)" + }, + "customer": { + "customer": "Stranka", + "customers": "Stranke", + "management": "Upravljanje strank", + "dashboard": "Nadzorna plošča stranke", + "dashboards": "Nadzorne plošče strank", + "devices": "Naprave za stranke", + "entity-views": "Pogledi stranke", + "assets": "Sredstva strank", + "public-dashboards": "Javne nadzorne plošče", + "public-devices": "Javne naprave", + "public-assets": "Javno sredstvo", + "public-entity-views": "Pogledi javnih subjektov", + "add": "Dodaj stranko", + "delete": "Izbriši stranko", + "manage-customer-users": "Upravljanje uporabniških strank", + "manage-customer-devices": "Upravljanje naprav strank", + "manage-customer-dashboards": "Upravljanje nadzornih plošč za stranke", + "manage-public-devices": "Upravljanje javnih naprav", + "manage-public-dashboards": "Upravljanje javnih nadzornih plošč", + "manage-customer-assets": "Upravljanje sredstev strank", + "manage-public-assets": "Upravljanje javnih sredstev", + "add-customer-text": "Dodaj novo stranko", + "no-customers-text": "Nobena stranka ni najdena", + "customer-details": "Podrobnosti o stranki", + "delete-customer-title": "Ali ste prepričani, da želite izbrisati stranko '{{customerTitle}}'?", + "delete-customer-text": "Previdno, po potrditvi bo stranka in vsi povezani podatki postali nepopravljivi.", + "delete-customers-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 customer} other {# customers} }?", + "delete-customers-action-title": "Izbriši { count, plural, 1 {1 customer} other {# customers} }", + "delete-customers-text": "Bodite previdni, po potrditvi bodo vse izbrane stranke odstranjene in vsi povezani podatki bodo postali nepopravljivi.", + "manage-users": "Upravljanje uporabnikov", + "manage-assets": "Upravljanje sredstev", + "manage-devices": "Upravljanje naprav", + "manage-dashboards": "Upravljanje nadzornih plošč", + "title": "Naslov", + "title-required": "Naslov je obvezen.", + "description": "Opis", + "details": "Podrobnosti", + "events": "Dogodki", + "copyId": "Kopiraj ID stranke", + "idCopiedMessage": "Id stranke je kopiran v odložišče", + "select-customer": "Izberi stranko", + "no-customers-matching": "Najdena ni bila nobena stranka, ki se ujema z '{{entity}}'.", + "customer-required": "Stranka je obvezna", + "select-default-customer": "Izberi privzeto stranko", + "default-customer": "Privzeta stranka", + "default-customer-required": "Za razhroščevanje nadzorne plošče na ravni najemnika je potrebna privzeta stranka", + "search": "Iskanje strank", + "selected-customers": "{ count, plural, 1 {1 customer} other {# customers} } izbrano" + }, + "datetime": { + "date-from": "Datum, od", + "time-from": "Čas od", + "date-to": "Datum do", + "time-to": "Čas do" + }, + "dashboard": { + "dashboard": "Nadzorna plošča", + "dashboards": "Nadzorne plošče", + "management": "Upravljanje z nadzorno ploščo", + "view-dashboards": "Ogled nadzornih plošč", + "add": "Dodaj nadzorno ploščo", + "assign-dashboard-to-customer": "Dodeli nadzorne plošče strankam", + "assign-dashboard-to-customer-text": "Izberite nadzorne plošče, ki jih želite dodeliti stranki", + "assign-to-customer-text": "Izberite stranko, ki bo dodelila nadzorno ploščo (-e)", + "assign-to-customer": "Dodeli stranki", + "unassign-from-customer": "Odvzemi stranki", + "make-public": "Naredi nadzorno ploščo javno", + "make-private": "Naj nadzorna plošča postane zasebna", + "manage-assigned-customers": "Upravljanje dodeljenih strank", + "assigned-customers": "Dodeljene stranke", + "assign-to-customers": "Dodeli nadzorne plošče strankam", + "assign-to-customers-text": "Izberite stranke, ki bodo dodelile nadzorno ploščo (-e)", + "unassign-from-customers": "Odstrani nadzorne plošče strankam", + "unassign-from-customers-text": "Na nadzorni plošči izberite stranke, ki jih želite odjaviti", + "no-dashboards-text": "Ni najdenih nadzornih plošč", + "no-widgets": "Pripomočki niso nastavljeni", + "add-widget": "Dodaj nov pripomoček", + "title": "Naslov", + "select-widget-title": "Izberi pripomoček", + "select-widget-subtitle": "Seznam razpoložljivih vrst gradnikov", + "delete": "Izbriši nadzorno ploščo", + "title-required": "Naslov je obvezen.", + "description": "Opis", + "details": "Podrobnosti", + "dashboard-details": "Podrobnosti o nadzorni plošči", + "add-dashboard-text": "Dodaj novo nadzorno ploščo", + "assign-dashboards": "Dodeli nadzorne plošče", + "assign-new-dashboard": "Dodeli novo nadzorno ploščo", + "assign-dashboards-text": "Strankam dodeli { count, plural, 1 {1 dashboard} other {# dashboards} }", + "unassign-dashboards-action-text": "Strankam ne dodeli { count, plural, 1 {1 dashboard} other {# dashboards} }", + "delete-dashboards": "Izbriši nadzorne plošče", + "unassign-dashboards": "Preklic dodelitve nadzornih plošč", + "unassign-dashboards-action-title": "Stranki odstrani { count, plural, 1 {1 dashboard} other {# dashboards} }", + "delete-dashboard-title": "Ali ste prepričani, da želite izbrisati nadzorno ploščo '{{dashboardTitle}}'?", + "delete-dashboard-text": "Previdno, po potrditvi bodo armaturna plošča in vsi povezani podatki nepopravljivi.", + "delete-dashboards-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 dashboard} other {# dashboards} }?", + "delete-dashboards-action-title": "Izbriši { count, plural, 1 {1 dashboard} other {# dashboards} }", + "delete-dashboards-text": "Bodite previdni, po potrditvi bodo vse izbrane nadzorne plošče odstranjene in vsi povezani podatki bodo postali nepopravljivi.", + "unassign-dashboard-title": "Ali ste prepričani, da želite odstraniti nadzorno ploščo '{{dashboardTitle}}'?", + "unassign-dashboard-text": "Po potrditvi bo nadzorna plošča odstranjena in nedostopna stranki.", + "unassign-dashboard": "Preklic dodelitve nadzorne plošče", + "unassign-dashboards-title": "Ali ste prepričani, da želite odstraniti { count, plural, 1 {1 dashboard} other {# dashboards} }?", + "unassign-dashboards-text": "Po potrditvi bodo vse izbrane nadzorne plošče odstranjene in nedostopne stranki.", + "public-dashboard-title": "Nadzorna plošča je zdaj javna", + "public-dashboard-text": "Vaša nadzorna plošča {{dashboardTitle}} je zdaj javna in dostopna prek naslednje javne povezave", + "public-dashboard-notice": "Opomba: Ne pozabite objaviti sorodnih naprav za dostop do njihovih podatkov.", + "make-private-dashboard-title": "Ali ste prepričani, da želite nadzorno ploščo '{{dashboardTitle}}' narediti zasebno?", + "make-private-dashboard-text": "Po potrditvi bo nadzorna plošča postala zasebna in ne bo dostopna drugim.", + "make-private-dashboard": "Naj nadzorna plošča postane zasebna", + "socialshare-text": "'{{dashboardTitle}}' poganja ThingsBoard", + "socialshare-title": "'{{dashboardTitle}}' poganja ThingsBoard", + "select-dashboard": "Izberi nadzorno ploščo", + "no-dashboards-matching": "Najdena ni bila nobena nadzorna plošča, ki se ujema z '{{entity}}'.", + "dashboard-required": "Potrebna je nadzorna plošča.", + "select-existing": "Izberi obstoječo nadzorno ploščo", + "create-new": "Ustvari novo nadzorno ploščo", + "new-dashboard-title": "Nov naslov nadzorne plošče", + "open-dashboard": "Odpri nadzorno ploščo", + "set-background": "Nastavi ozadje", + "background-color": "Barva ozadja", + "background-image": "Slika ozadja", + "background-size-mode": "Način velikosti ozadja", + "no-image": "Izbrana ni nobena slika", + "drop-image": "Spustite sliko ali kliknite, da izberete datoteko za nalaganje.", + "settings": "Nastavitve", + "columns-count": "Število stolpcev", + "columns-count-required": "Število stolpcev je obvezno.", + "min-columns-count-message": "Dovoljeno je samo 10 najmanjših števcev stolpcev.", + "max-columns-count-message": "Dovoljeno je samo 1000 največje število stolpcev.", + "widgets-margins": "Razmik med pripomočki", + "margin-required": "Zahtevana je vrednost razmika.", + "min-margin-message": "Najmanjša dovoljena vrednost razmika je 0.", + "max-margin-message": "Največja dovoljena vrednost razmika je 50.", + "horizontal-margin": "Vodoravni razmik", + "horizontal-margin-required": "Zahtevana je horizontalna vrednost razmika.", + "min-horizontal-margin-message": "Najmanjša dovoljena vrednost vodoravnega razmika je 0.", + "max-horizontal-margin-message": "Največja dovoljena vrednost vodoravnega razmika je 50.", + "vertical-margin": "Navpični razmik", + "vertical-margin-required": "Zahtevana je navpična vrednost razmika.", + "min-vertical-margin-message": "Najmanjša dovoljena vrednost navpičnega razmika je 0.", + "max-vertical-margin-message": "Največja dovoljena vrednost navpičnega razmika je 50.", + "autofill-height": "Samodejno polnjenje višine postavitve", + "mobile-layout": "Nastavitve mobilne postavitve", + "mobile-row-height": "Višina mobilne vrstice, px", + "mobile-row-height-required": "Vrednost višine mobilne vrstice je obvezna.", + "min-mobile-row-height-message": "Najmanjša dovoljena vrednost višine mobilne vrstice je 5 slikovnih pik.", + "max-mobile-row-height-message": "Največja dovoljena vrednost višine mobilne vrstice je 200 slikovnih pik.", + "display-title": "Prikaži naslov nadzorne plošče", + "toolbar-always-open": "Orodna vrstica naj bo odprta", + "title-color": "Barva naslova", + "display-dashboards-selection": "Prikaži izbiro nadzornih plošč", + "display-entities-selection": "Prikaži izbor entitet", + "display-filters": "Prikaži filtre", + "display-dashboard-timewindow": "Prikaži časovno okno", + "display-dashboard-export": "Prikaži izvoz", + "import": "Uvozi nadzorno ploščo", + "export": "Izvozi nadzorno ploščo", + "export-failed-error": "Nadzorne plošče ni mogoče izvoziti: {{error}}", + "create-new-dashboard": "Ustvari novo nadzorno ploščo", + "dashboard-file": "Datoteka nadzorne plošče", + "invalid-dashboard-file-error": "Nadzorne plošče ni mogoče uvoziti: neveljavna struktura podatkov na nadzorni plošči.", + "dashboard-import-missing-aliases-title": "Konfiguriranje vzdevkov, ki jih uporablja uvožena nadzorna plošča", + "create-new-widget": "Ustvari nov pripomoček", + "import-widget": "Uvozi pripomoček", + "widget-file": "Datoteka pripomočka", + "invalid-widget-file-error": "Pripomočka ni mogoče uvoziti: neveljavna struktura podatkov pripomočka.", + "widget-import-missing-aliases-title": "Konfiguriranje vzdevkov, ki jih uporablja uvoženi gradnik", + "open-toolbar": "Odpri orodno vrstico armaturne plošče", + "close-toolbar": "Zapri orodno vrstico", + "configuration-error": "Napaka v konfiguraciji", + "alias-resolution-error-title": "Napaka pri konfiguraciji vzdevkov na nadzorni plošči", + "invalid-aliases-config": "Ni mogoče najti nobene naprave, ki se ujema z nekaterimi filtri vzdevkov.
Prosimo, obrnite se na skrbnika, da odpravite to težavo.", + "select-devices": "Izberi naprave", + "assignedToCustomer": "Dodeljeno stranki", + "assignedToCustomers": "Dodeljeno strankam", + "public": "Javno", + "public-link": "Javna povezava", + "copy-public-link": "Kopiraj javno povezavo", + "public-link-copied-message": "Javna povezava na nadzorni plošči je bila kopirana v odložišče", + "manage-states": "Upravljanje stanj nadzorne plošče", + "states": "Stanja na nadzorni plošči", + "search-states": "Išči stanja na nadzorni plošči", + "selected-states": "{ count, plural, 1 {1 dashboard state} other {# dashboard state} } izbrano", + "edit-state": "Uredi stanje nadzorne plošče", + "delete-state": "Izbriši stanje armaturne plošče", + "add-state": "Dodaj stanje nadzorne plošče", + "no-states-text": "Ni nobene države", + "state": "Stanje nadzorne plošče", + "state-name": "Ime", + "state-name-required": "Ime stanja nadzorne plošče je obvezno.", + "state-id": "ID države", + "state-id-required": "ID stanja nadzorne plošče je obvezen.", + "state-id-exists": "Stanje nadzorne plošče z istim ID že obstaja.", + "is-root-state": "Izvorno stanje", + "delete-state-title": "Izbriši stanje nadzorne plošče", + "delete-state-text": "Ali ste prepričani, da želite izbrisati stanje nadzorne plošče z imenom '{{stateName}}'?", + "show-details": "Pokaži podrobnosti", + "hide-details": "Skrij podrobnosti", + "select-state": "Izberi ciljno stanje", + "state-controller": "Državni nadzornik", + "search": "Iskanje po nadzornih ploščah", + "selected-dashboards": "{ count, plural, 1 {1 dashboard} other {# dashboards} } izbrano" + }, + "datakey": { + "settings": "Nastavitve", + "advanced": "Napredno", + "label": "Oznaka", + "color": "Barva", + "units": "Poseben simbol za prikaz poleg vrednosti", + "decimals": "Število števk po plavajoči vejici", + "data-generation-func": "Funkcija ustvarjanja podatkov", + "use-data-post-processing-func": "Uporabi funkcijo naknadne obdelave podatkov", + "configuration": "Konfiguracija podatkovnega ključa", + "timeseries": "Časovne serije", + "attributes": "Lastnosti", + "entity-field": "Polje entitete", + "alarm": "Polja alarma", + "timeseries-required": "Potrebni so časovni nizi entitet.", + "timeseries-or-attributes-required": "Potrebni so časovni nizi / atributi entitet.", + "alarm-fields-timeseries-or-attributes-required": "Polja alarma ali časovni nizi / atributi entitet so obvezni.", + "maximum-timeseries-or-attributes": "Največ { count, plural, 1 {1 timeseries / attribute is allowed.} other {# timeseries / attributes are allowed} }", + "alarm-fields-required": "Polja alarma so obvezna.", + "function-types": "Vrste funkcij", + "function-types-required": "Zahtevane so vrste funkcij.", + "maximum-function-types": "Največ dovoljeno je { count, plural, 1 {1 type of function.} other {# vrste funkcij so dovoljene} }", + "time-description": "časovni žig trenutne vrednosti;", + "value-description": "trenutna vrednost;", + "prev-value-description": "rezultat prejšnjega klica funkcije;", + "time-prev-description": "časovni žig prejšnje vrednosti;", + "prev-orig-value-description": "prvotna prejšnja vrednost;" + }, + "datasource": { + "type": "Vrsta vira podatkov", + "name": "Ime", + "add-datasource-prompt": "Prosimo, dodajte vir podatkov" + }, + "details": { + "details": "Podrobnosti", + "edit-mode": "Način urejanja", + "edit-json": "Uredi JSON", + "toggle-edit-mode": "Preklopi način urejanja" + }, + "device": { + "device": "Naprava", + "device-required": "Naprava je potrebna.", + "devices": "Naprave", + "management": "Upravljanje naprav", + "view-devices": "Ogled naprav", + "device-alias": "Vzdevek naprave", + "aliases": "Vzdevki naprav", + "no-alias-matching": "'{{alias}}' ni mogoče najti.", + "no-aliases-found": "Vzdevkov ni bilo mogoče najti.", + "no-key-matching": "'{{key}}' ni mogoče najti.", + "no-keys-found": "Ključev ni bilo mogoče najti.", + "create-new-alias": "Ustvari novega!", + "create-new-key": "Ustvari novega!", + "duplicate-alias-error": "Najden je podvojen vzdevek '{{alias}}'.
Vzdevki naprav morajo biti na nadzorni plošči edinstveni.", + "configure-alias": "Konfiguriraj vzdevek '{{alias}}'", + "no-devices-matching": "Najdena ni bila nobena naprava, ki se ujema z '{{entity}}'.", + "alias": "Vzdevek", + "alias-required": "Vzdevek naprave je obvezen.", + "remove-alias": "Odstrani vzdevek naprave", + "add-alias": "Dodaj vzdevek naprave", + "name-starts-with": "Ime naprave se začne z", + "device-list": "Seznam naprav", + "use-device-name-filter": "Uporabi filter", + "device-list-empty": "Izbrana ni nobena naprava.", + "device-name-filter-required": "Potreben je filter imena naprave.", + "device-name-filter-no-device-matched": "Najdena ni bila nobena naprava, ki se začne z '{{device}}'.", + "add": "Dodaj napravo", + "assign-to-customer": "Dodeli stranki", + "assign-device-to-customer": "Dodeli naprave strankam", + "assign-device-to-customer-text": "Izberite naprave, ki jih želite dodeliti stranki", + "make-public": "Naredi napravo javno", + "make-private": "Naj bo naprava zasebna", + "no-devices-text": "Naprave ni bilo mogoče najti", + "assign-to-customer-text": "Prosimo, izberite stranko, ki bo dodelila naprave (-e)", + "device-details": "Podrobnosti o napravi", + "add-device-text": "Dodaj novo napravo", + "credentials": "Poverilnice", + "manage-credentials": "Upravljanje poverilnic", + "delete": "Izbriši napravo", + "assign-devices": "Dodeli naprave", + "assign-devices-text": "Kupcu dodeli { count, plural, 1 {1 device} other {# devices} }", + "delete-devices": "Izbriši naprave", + "unassign-from-customer": "Odjavi od stranke", + "unassign-devices": "Odjavi naprave", + "unassign-devices-action-title": "Odstrani { count, plural, 1 {1 device} other {# devices} } od stranke", + "assign-new-device": "Dodeli novo napravo", + "make-public-device-title": "Ali ste prepričani, da želite napravo '{{deviceName}}' narediti javno?", + "make-public-device-text": "Po potrditvi bo naprava in vsi njeni podatki javno dostopni drugim.", + "make-private-device-title": "Ali ste prepričani, da želite napravo '{{deviceName}}' narediti zasebno?", + "make-private-device-text": "Po potrditvi bo naprava in vsi njeni podatki postali zasebni in nedostopni drugim.", + "view-credentials": "Ogled poverilnic", + "delete-device-title": "Ali ste prepričani, da želite izbrisati napravo '{{deviceName}}'?", + "delete-device-text": "Previdno, po potrditvi naprava in vsi povezani podatki postanejo nepopravljivi.", + "delete-devices-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 device} other {# naprav} }?", + "delete-devices-action-title": "Izbriši { count, plural, 1 {1 device} other {# devices} }", + "delete-devices-text": "Bodite previdni, po potrditvi bodo vse izbrane naprave odstranjene in vsi povezani podatki nepopravljivi.", + "unassign-device-title": "Ali ste prepričani, da želite odstraniti napravo '{{deviceName}}'?", + "unassign-device-text": "Po potrditvi naprava ne bo dodeljena in nedostopna stranki.", + "unassign-device": "Preklic dodelitve naprave", + "unassign-devices-title": "Ali ste prepričani, da želite odstraniti { count, plural, 1 {1 device} other {# naprave} }?", + "unassign-devices-text": "Po potrditvi bodo vse izbrane naprave nedodeljene in nedostopne stranki.", + "device-credentials": "Poverilnice naprave", + "credentials-type": "Vrsta poverilnic", + "access-token": "Dostopni žeton", + "access-token-required": "Zahtevan je žeton za dostop.", + "access-token-invalid": "Dolžina žetona za dostop mora biti od 1 do 20 znakov.", + "rsa-key": "Javni ključ RSA", + "rsa-key-required": "Potreben je javni ključ RSA.", + "client-id": "Client ID", + "client-id-pattern": "Contains invalid character.", + "user-name": "User Name", + "user-name-required": "User Name is required.", + "client-id-or-user-name-necessary": "Client ID and/or User Name are necessary", + "password": "Password", + "secret": "Skrivnost", + "secret-required": "Potrebna je skrivnost.", + "device-type": "Vrsta naprave", + "device-type-required": "Zahtevana je vrsta naprave.", + "select-device-type": "Izberite vrsto naprave", + "enter-device-type": "Vnesite vrsto naprave", + "any-device": "Katerakoli naprava", + "no-device-types-matching": "Najdena ni bila nobena vrsta naprave, ki se ujema z '{{entitySubtype}}'.", + "device-type-list-empty": "Izbrana ni nobena vrsta naprave.", + "device-types": "Vrste naprav", + "name": "Ime", + "name-required": "Ime je obvezno.", + "description": "Opis", + "label": "Oznaka", + "events": "Dogodki", + "details": "Podrobnosti", + "copyId": "Kopiraj ID naprave", + "copyAccessToken": "Kopiraj žeton za dostop", + "copy-mqtt-authentication": "Copy MQTT credentials", + "idCopiedMessage": "ID naprave je bil kopiran v odložišče", + "accessTokenCopiedMessage": "Žeton za dostop do naprave je bil kopiran v odložišče", + "mqtt-authentication-copied-message": "Device MQTT authentication has been copied to clipboard", + "assignedToCustomer": "Dodeljeno stranki", + "unable-delete-device-alias-title": "Vzdevka naprave ni mogoče izbrisati", + "unable-delete-device-alias-text": "Vzdevka naprave '{{deviceAlias}}' ni mogoče izbrisati, saj ga uporabljajo naslednji pripomočki:
{{widgetsList}}", + "is-gateway": "Je prehod", + "public": "Javno", + "device-public": "Naprava je javna", + "select-device": "Izberi napravo", + "import": "Uvozi napravo", + "device-file": "Datoteka naprave", + "search": "Iskalne naprave", + "selected-devices": "{ count, plural, 1 {1 device} other {# devices} } izbrano", + "device-configuration": "Device configuration", + "transport-configuration": "Transport configuration", + "wizard": { + "device-wizard": "Device Wizard", + "device-details": "Device details", + "new-device-profile": "Create new device profile", + "existing-device-profile": "Select existing device profile", + "specific-configuration": "Specific configuration", + "customer-to-assign-device": "Customer to assign the device", + "add-credential": "Add credential" + } + }, + "device-profile": { + "device-profile": "Device profile", + "device-profiles": "Device profiles", + "all-device-profiles": "All", + "add": "Add device profile", + "edit": "Edit device profile", + "device-profile-details": "Device profile details", + "no-device-profiles-text": "No device profiles found", + "search": "Search device profiles", + "selected-device-profiles": "{ count, plural, 1 {1 device profile} other {# device profiles} } selected", + "no-device-profiles-matching": "No device profile matching '{{entity}}' were found.", + "device-profile-required": "Device profile is required", + "idCopiedMessage": "Device profile Id has been copied to clipboard", + "set-default": "Make device profile default", + "delete": "Delete device profile", + "copyId": "Copy device profile Id", + "new-device-profile-name": "Device profile name", + "new-device-profile-name-required": "Device profile name is required.", + "name": "Name", + "name-required": "Name is required.", + "type": "Profile type", + "type-required": "Profile type is required.", + "type-default": "Default", + "transport-type": "Transport type", + "transport-type-required": "Transport type is required.", + "transport-type-default": "Default", + "transport-type-default-hint": "Supports basic MQTT, HTTP and CoAP transport", + "transport-type-mqtt": "MQTT", + "transport-type-mqtt-hint": "Enables advanced MQTT transport settings", + "transport-type-lwm2m": "LWM2M", + "transport-type-lwm2m-hint": "LWM2M transport type", + "description": "Description", + "default": "Default", + "profile-configuration": "Profile configuration", + "transport-configuration": "Transport configuration", + "default-rule-chain": "Default rule chain", + "select-queue-hint": "Select from a drop-down list or add a custom name.", + "delete-device-profile-title": "Are you sure you want to delete the device profile '{{deviceProfileName}}'?", + "delete-device-profile-text": "Be careful, after the confirmation the device profile and all related data will become unrecoverable.", + "delete-device-profiles-title": "Are you sure you want to delete { count, plural, 1 {1 device profile} other {# device profiles} }?", + "delete-device-profiles-text": "Be careful, after the confirmation all selected device profiles will be removed and all related data will become unrecoverable.", + "set-default-device-profile-title": "Are you sure you want to make the device profile '{{deviceProfileName}}' default?", + "set-default-device-profile-text": "After the confirmation the device profile will be marked as default and will be used for new devices with no profile specified.", + "no-device-profiles-found": "No device profiles found.", + "create-new-device-profile": "Create a new one!", + "mqtt-device-topic-filters": "MQTT device topic filters", + "mqtt-device-topic-filters-unique": "MQTT device topic filters need to be unique.", + "mqtt-device-payload-type": "MQTT device payload", + "mqtt-device-payload-type-json": "JSON", + "mqtt-device-payload-type-proto": "Protobuf", + "mqtt-payload-type-required": "Payload type is required.", + "support-level-wildcards": "Single [+] and multi-level [#] wildcards supported.", + "telemetry-topic-filter": "Telemetry topic filter", + "telemetry-topic-filter-required": "Telemetry topic filter is required.", + "attributes-topic-filter": "Attributes topic filter", + "attributes-topic-filter-required": "Attributes topic filter is required.", + "telemetry-proto-schema": "Telemetry proto schema", + "telemetry-proto-schema-required": "Telemetry proto schema is required.", + "attributes-proto-schema": "Attributes proto schema", + "attributes-proto-schema-required": "Attributes proto schema is required.", + "rpc-response-topic-filter": "RPC response topic filter", + "rpc-response-topic-filter-required": "RPC response topic filter is required.", + "not-valid-pattern-topic-filter": "Not valid pattern topic filter", + "not-valid-single-character": "Invalid use of a single-level wildcard character", + "not-valid-multi-character": "Invalid use of a multi-level wildcard character", + "single-level-wildcards-hint": "[+] is suitable for any topic filter level. Ex.: v1/devices/+/telemetry or +/devices/+/attributes.", + "multi-level-wildcards-hint": "[#] can replace the topic filter itself and must be the last symbol of the topic. Ex.: # or v1/devices/me/#.", + "alarm-rules": "Alarm rules", + "alarm-rules-with-count": "Alarm rules ({{count}})", + "no-alarm-rules": "No alarm rules configured", + "add-alarm-rule": "Add alarm rule", + "edit-alarm-rule": "Edit alarm rule", + "alarm-type": "Alarm type", + "alarm-type-required": "Alarm type is required.", + "alarm-type-unique": "Alarm type must be unique within the device profile alarm rules.", + "create-alarm-pattern": "Create {{alarmType}} alarm", + "create-alarm-rules": "Create alarm rules", + "no-create-alarm-rules": "No create conditions configured", + "add-create-alarm-rule-prompt": "Please add create alarm rule", + "clear-alarm-rule": "Clear alarm rule", + "no-clear-alarm-rule": "No clear condition configured", + "add-create-alarm-rule": "Add create condition", + "add-clear-alarm-rule": "Add clear condition", + "select-alarm-severity": "Select alarm severity", + "alarm-severity-required": "Alarm severity is required.", + "condition-duration": "Condition duration", + "condition-duration-value": "Duration value", + "condition-duration-time-unit": "Time unit", + "condition-duration-value-range": "Duration value should be in a range from 1 to 2147483647.", + "condition-duration-value-pattern": "Duration value should be integers.", + "condition-duration-value-required": "Duration value is required.", + "condition-duration-time-unit-required": "Time unit is required.", + "advanced-settings": "Advanced settings", + "alarm-rule-details": "Details", + "add-alarm-rule-details": "Add details", + "propagate-alarm": "Propagate alarm", + "alarm-rule-relation-types-list": "Relation types to propagate", + "alarm-rule-relation-types-list-hint": "If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.", + "alarm-details": "Alarm details", + "alarm-rule-condition": "Alarm rule condition", + "enter-alarm-rule-condition-prompt": "Please add alarm rule condition", + "edit-alarm-rule-condition": "Edit alarm rule condition", + "device-provisioning": "Device provisioning", + "provision-strategy": "Provision strategy", + "provision-strategy-required": "Provision strategy is required.", + "provision-strategy-disabled": "Disabled", + "provision-strategy-created-new": "Allow to create new devices", + "provision-strategy-check-pre-provisioned": "Check for pre-provisioned devices", + "provision-device-key": "Provision device key", + "provision-device-key-required": "Provision device key is required.", + "copy-provision-key": "Copy provision key", + "provision-key-copied-message": "Provision key has been copied to clipboard", + "provision-device-secret": "Provision device secret", + "provision-device-secret-required": "Provision device secret is required.", + "copy-provision-secret": "Copy provision secret", + "provision-secret-copied-message": "Provision secret has been copied to clipboard", + "condition": "Condition", + "condition-type": "Condition type", + "condition-type-simple": "Simple", + "condition-type-duration": "Duration", + "condition-during": "During {{during}}", + "condition-type-repeating": "Repeating", + "condition-type-required": "Condition type is required.", + "condition-repeating-value": "Count of events", + "condition-repeating-value-range": "Count of events should be in a range from 1 to 2147483647.", + "condition-repeating-value-pattern": "Count of events should be integers.", + "condition-repeating-value-required": "Count of events is required.", + "condition-repeat-times": "Repeats { count, plural, 1 {1 time} other {# times} }", + "schedule-type": "Scheduler type", + "schedule-type-required": "Scheduler type is required.", + "schedule": "Schedule", + "edit-schedule": "Edit alarm schedule", + "schedule-any-time": "Active all the time", + "schedule-specific-time": "Active at a specific time", + "schedule-custom": "Custom", + "schedule-day": { + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday" + }, + "schedule-days": "Days", + "schedule-time": "Time", + "schedule-time-from": "From", + "schedule-time-to": "To", + "schedule-days-of-week-required": "At least one day of week should be selected." + }, + "dialog": { + "close": "Zapri pogovorno okno" + }, + "direction": { + "column": "Stolpec", + "row": "Vrstica" + }, + "error": { + "unable-to-connect": "Povezave s strežnikom ni mogoče vzpostaviti! Preverite internetno povezavo.", + "unhandled-error-code": "Neobdelana koda napake: {{errorCode}}", + "unknown-error": "Neznana napaka" + }, + "entity": { + "entity": "Entiteta", + "entities": "Subjekti", + "aliases": "Vzdevki entitet", + "entity-alias": "Vzdevek entitete", + "unable-delete-entity-alias-title": "Vzdevka entitete ni mogoče izbrisati", + "unable-delete-entity-alias-text": "Vzdevka entitete '{{entityAlias}}' ni mogoče izbrisati, ker ga uporabljajo naslednji pripomočki:
{{widgetsList}}", + "duplicate-alias-error": "Najden je podvojen vzdevek '{{alias}}'.
Vzdevki entitet morajo biti na nadzorni plošči edinstveni.", + "missing-entity-filter-error": "Za vzdevek '{{alias}}' manjka filter.", + "configure-alias": "Konfiguriraj vzdevek '{{alias}}'", + "alias": "Vzdevek", + "alias-required": "Zahtevan je vzdevek entitete.", + "remove-alias": "Odstrani vzdevek entitete", + "add-alias": "Dodaj vzdevek entitete", + "entity-list": "Seznam entitet", + "entity-type": "Vrsta entitete", + "entity-types": "Vrste entitet", + "entity-type-list": "Seznam entitet", + "any-entity": "Katerikoli subjekt", + "enter-entity-type": "Vnesite vrsto entitete", + "no-entities-matching": "Najdena ni bila nobena enota, ki se ujema z '{{entity}}'.", + "no-entity-types-matching": "Najdena ni bila nobena vrsta entitete, ki se ujema z '{{entityType}}'.", + "name-starts-with": "Ime se začne z", + "use-entity-name-filter": "Uporabi filter", + "entity-list-empty": "Nobena entiteta ni izbrana.", + "entity-type-list-empty": "Izbrana ni nobena vrsta entitete.", + "entity-name-filter-required": "Potreben je filter imena entitete.", + "entity-name-filter-no-entity-matched": "Najti ni bilo nobene entitete, ki se začne z '{{entity}}'.", + "all-subtypes": "Vse", + "select-entities": "Izberi entitete", + "no-aliases-found": "Vzdevkov ni bilo mogoče najti.", + "no-alias-matching": "'{{alias}}' ni mogoče najti.", + "create-new-alias": "Ustvari novega!", + "key": "Ključ", + "key-name": "Ime ključa", + "no-keys-found": "Ključev ni bilo mogoče najti.", + "no-key-matching": "'{{key}}' ni mogoče najti.", + "create-new-key": "Ustvari novega!", + "type": "Vrsta", + "type-required": "Zahteva se vrsta entitete.", + "type-device": "Naprava", + "type-devices": "Naprave", + "list-of-devices": "{ count, plural, 1 {One device} other {List of # devices} }", + "device-name-starts-with": "Naprave, katerih imena se začnejo z '{{prefix}}'", + "type-device-profile": "Device profile", + "type-device-profiles": "Device profiles", + "list-of-device-profiles": "{ count, plural, 1 {One device profile} other {List of # device profiles} }", + "device-profile-name-starts-with": "Device profiles whose names start with '{{prefix}}'", + "type-asset": "Sredstvo", + "type-assets": "Sredstva", + "list-of-assets": "{ count, plural, 1 {One asset} other {Seznam # sredstev} }", + "asset-name-starts-with": "Sredstva, katerih imena se začnejo z '{{prefix}}'", + "type-entity-view": "Pogled entitete", + "type-entity-views": "Pogledi entitet", + "list-of-entity-views": "{ count, plural, 1 {One entity view} other {Seznam # pogledov entitet} }", + "entity-view-name-starts-with": "Pogledi entitet, katerih imena se začnejo z '{{prefix}}'", + "type-rule": "Pravilo", + "type-rules": "Pravila", + "list-of-rules": "{ count, plural, 1 {One rule} other {Seznam # pravil} }", + "rule-name-starts-with": "Pravila, katerih imena se začnejo z '{{prefix}}'", + "type-plugin": "Vključiti", + "type-plugins": "Vtičniki", + "list-of-plugins": "{ count, plural, 1 {One plugin} other {List of # plugins} }", + "plugin-name-starts-with": "Vtičniki, katerih imena se začnejo z '{{prefix}}'", + "type-tenant": "Najemnik", + "type-tenants": "Najemniki", + "list-of-tenants": "{ count, plural, 1 {One najemnik} other {Seznam # najemnikov} }", + "tenant-name-starts-with": "Najemniki, katerih imena se začnejo z '{{prefix}}'", + "type-tenant-profile": "Tenant profile", + "type-tenant-profiles": "Tenant profiles", + "list-of-tenant-profiles": "{ count, plural, 1 {One tenant profile} other {List of # tenant profiles} }", + "tenant-profile-name-starts-with": "Tenant profiles whose names start with '{{prefix}}'", + "type-customer": "Stranka", + "type-customers": "Stranke", + "list-of-customers": "{ count, plural, 1 {One customer} other {List of # customers} }", + "customer-name-starts-with": "Stranke, katerih imena se začnejo z '{{prefix}}'", + "type-user": "Uporabnik", + "type-users": "Uporabniki", + "list-of-users": "{ count, plural, 1 {One user} other {List of # users} }", + "user-name-starts-with": "Uporabniki, katerih imena se začnejo z '{{prefix}}'", + "type-dashboard": "Nadzorna plošča", + "type-dashboards": "Nadzorne plošče", + "list-of-dashboards": "{ count, plural, 1 {One dashboard} other {List of # dashboards} }", + "dashboard-name-starts-with": "Nadzorne plošče, katerih imena se začnejo z '{{prefix}}'", + "type-alarm": "Alarm", + "type-alarms": "Alarmi", + "list-of-alarms": "{ count, plural, 1 {One alarms} other {List of # alarms} }", + "alarm-name-starts-with": "Alarmi, katerih imena se začnejo z '{{prefix}}'", + "type-rulechain": "Veriga pravil", + "type-rulechains": "Pravila", + "list-of-rulechains": "{ count, plural, 1 {One rule chain} other {List of # rule chains} }", + "rulechain-name-starts-with": "Verige pravil, katerih imena se začnejo z '{{prefix}}'", + "type-rulenode": "Vozlišče pravila", + "type-rulenodes": "Vozlišča pravil", + "list-of-rulenodes": "{ count, plural, 1 {One rule node} other {List of # rule nodes} }", + "rulenode-name-starts-with": "Vozlišča pravil, katerih imena se začnejo z '{{prefix}}'", + "type-current-customer": "Trenutna stranka", + "type-current-tenant": "Trenutni najemnik", + "type-current-user": "Trenutni uporabnik", + "type-current-user-owner": "Trenutni lastnik uporabnika", + "search": "Iskanje entitet", + "selected-entities": "{ count, plural, 1 {1 entity} other {# entitet} } izbranih", + "entity-name": "Ime entitete", + "entity-label": "Oznaka entitete", + "details": "Podrobnosti o entiteti", + "no-entities-prompt": "Ni entitet", + "no-data": "Ni podatkov za prikaz", + "columns-to-display": "Stolpci za prikaz", + "type-api-usage-state": "Api Usage State" + }, + "entity-field": { + "created-time": "Čas ustvaritve", + "name": "Ime", + "type": "Vrsta", + "first-name": "Ime", + "last-name": "Priimek", + "email": "E-naslov", + "title": "Naziv", + "country": "Država", + "state": "Država", + "city": "Mesto", + "address": "Naslov", + "address2": "Naslov 2", + "zip": "Poštna številka", + "phone": "Telefon", + "label": "Oznaka" + }, + "entity-view": { + "entity-view": "Pogled entitete", + "entity-view-required": "Zahtevan je pogled entitete.", + "entity-views": "Pogledi entitet", + "management": "Upravljanje entitetnega pogleda", + "view-entity-views": "Ogled pogledov entitete", + "entity-view-alias": "Vzdevek entitetnega pogleda", + "aliases": "Vzdevki entitetnega pogleda", + "no-alias-matching": "'{{alias}}' ni mogoče najti.", + "no-aliases-found": "Vzdevkov ni bilo mogoče najti.", + "no-key-matching": "'{{key}}' ni mogoče najti.", + "no-keys-found": "Ključev ni bilo mogoče najti.", + "create-new-alias": "Ustvari novega!", + "create-new-key": "Ustvari novega!", + "duplicate-alias-error": "Najden je podvojen vzdevek '{{alias}}'.
Vzdevki entitetnega pogleda na nadzorni plošči morajo biti edinstveni.", + "configure-alias": "Konfiguriraj vzdevek '{{alias}}'", + "no-entity-views-matching": "Najden ni bil noben pogled entitete, ki se ujema z '{{entity}}'.", + "public": "Javno", + "alias": "Vzdevek", + "alias-required": "Zahtevan je vzdevek pogleda entitete.", + "remove-alias": "Odstrani vzdevek pogleda entitete", + "add-alias": "Dodaj vzdevek pogleda entitete", + "name-starts-with": "Ime entitete se začne z", + "entity-view-list": "Seznam entitetnega pogleda", + "use-entity-view-name-filter": "Uporabi filter", + "entity-view-list-empty": "Izbran ni noben pogled entitete.", + "entity-view-name-filter-required": "Potreben je filter imena pogleda entitete.", + "entity-view-name-filter-no-entity-view-matched": "Najden ni bil noben pogled entitete, ki se začne z '{{entityView}}'.", + "add": "Dodaj pogled entitete", + "entity-view-public": "Pogled entitete je javen", + "assign-to-customer": "Dodeli stranki", + "assign-entity-view-to-customer": "Dodelitev pogledov entitet strankam", + "assign-entity-view-to-customer-text": "Izberite poglede entitet, ki jih želite dodeliti stranki", + "no-entity-views-text": "Ni najden noben pogled entitete", + "assign-to-customer-text": "Izberite stranko, ki bo dodelila poglede entitet", + "entity-view-details": "Podrobnosti o pogledu entitete", + "add-entity-view-text": "Dodaj nov pogled entitete", + "delete": "Izbriši pogled entitete", + "assign-entity-views": "Dodelitev pogledov entitete", + "assign-entity-views-text": "Stranki dodeli { count, plural, 1 {1 entity view} other {# entity views} }", + "delete-entity-views": "Izbriši poglede entitet", + "unassign-from-customer": "Odstrani od stranke", + "unassign-entity-views": "Odstrani poglede entitete", + "unassign-entity-views-action-title": "Od stranke odstrani { count, plural, 1 {1 entity view} other {# entity views} }", + "assign-new-entity-view": "Dodeli nov pogled entitete", + "delete-entity-view-title": "Ali ste prepričani, da želite izbrisati pogled entitete '{{entityViewName}}'?", + "delete-entity-view-text": "Bodite previdni, po potrditvi bodo pogled entitete in vsi povezani podatki nepopravljivi.", + "delete-entity-views-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 entity view} other {# entity views} }?", + "delete-entity-views-action-title": "Izbriši { count, plural, 1 {1 entity view} other {# entity views} }", + "delete-entity-views-text": "Bodite previdni, po potrditvi bodo odstranjeni vsi pogledi izbranih entitet in vsi povezani podatki bodo postali nepopravljivi.", + "unassign-entity-view-title": "Ali ste prepričani, da želite odstraniti pogled entitete '{{entityViewName}}?", + "unassign-entity-view-text": "Po potrditvi bo pogled entitete odstranjen in nedostopen stranki.", + "unassign-entity-view": "Odstrani pogled entitete", + "unassign-entity-views-title": "Ali ste prepričani, da želite odstraniti { count, plural, 1 {1 entity view} other {# entity views} }?", + "unassign-entity-views-text": "Po potrditvi bodo vsi pogledi izbranih entitet odstranjeni in stranki nedostopni.", + "entity-view-type": "Vrsta pogleda entitete", + "entity-view-type-required": "Zahteva se vrsta pogleda entitete.", + "select-entity-view-type": "Izberi vrsto pogleda entitete", + "enter-entity-view-type": "Vnesite vrsto pogleda entitete", + "any-entity-view": "Katerikoli pogled entitete", + "no-entity-view-types-matching": "Najdena ni bila nobena vrsta pogleda entitete, ki se ujema z '{{entitySubtype}}'.", + "entity-view-type-list-empty": "Izbrana ni nobena vrsta pogleda entitete.", + "entity-view-types": "Vrste pogleda entitete", + "created-time": "Ustvarjeni čas", + "name": "Ime", + "name-required": "Ime je obvezno.", + "description": "Opis", + "events": "Dogodki", + "details": "Podrobnosti", + "copyId": "Kopiraj ID pogleda entitete", + "idCopiedMessage": "ID entitete je kopiran v odložišče", + "assignedToCustomer": "Dodeljeno stranki", + "unable-entity-view-device-alias-title": "Ni mogoče izbrisati vzdevka pogleda entitete", + "unable-entity-view-device-alias-text": "Vzdevka naprave '{{entityViewAlias}}' ni mogoče izbrisati, saj ga uporabljajo naslednji pripomočki:
{{widgetsList}}", + "select-entity-view": "Izberi pogled entitete", + "make-public": "Naj bo pogled entitete javen", + "make-private": "Naj bo pogled entitete zaseben", + "start-date": "Začetni datum", + "start-ts": "Začetni čas", + "end-date": "Končni datum", + "end-ts": "Končni čas", + "date-limits": "Datumske omejitve", + "client-attributes": "Atributi odjemalca", + "shared-attributes": "Skupni atributi", + "server-attributes": "Atributi strežnika", + "timeseries": "Časovne serije", + "client-attributes-placeholder": "Atributi odjemalca", + "shared-attributes-placeholder": "Skupni atributi", + "server-attributes-placeholder": "Atributi strežnika", + "timeseries-placeholder": "Časovne serije", + "target-entity": "Ciljna entiteta", + "attributes-propagation": "Propragacija atributov", + "attributes-propagation-hint": "Pogled entitete bo samodejno kopiral določene atribute iz ciljne entitete vsakič, ko shranite ali posodobite ta pogled entitete. Zaradi učinkovitosti se atributi ciljne entitete ne propagirajo v pogled entitete pri vsaki spremembi atributa. Samodejno propagacijo lahko omogočite tako, da konfigurirate \" kopiraj v ogled\" vozlišče pravila v verigi pravil in povezovanje sporočil \"Objavi atribute\" in \"Atributi posodobljeni\" na novo vozlišče pravila.", + "timeseries-data": "Podatki časovnih serij", + "timeseries-data-hint": "Konfigurirajte podatkovne ključe časovnih serij ciljne entitete, ki bodo dostopne pogledu entitete. Ti podatki časovnih serij so samo za branje.", + "make-public-entity-view-title": "Ali ste prepričani, da želite pogled entitete '{{entityViewName}}' narediti javen?", + "make-public-entity-view-text": "Po potrditvi bodo pogled entitete in vsi njegovi podatki javni in dostopni drugim.", + "make-private-entity-view-title": "Ali ste prepričani, da želite pogled entitete '{{entityViewName}}' narediti zaseben?", + "make-private-entity-view-text": "Po potrditvi bodo pogled entitete in vsi njegovi podatki postali zasebni in drugim nedostopni.", + "search": "Išči poglede entitet", + "selected-entity-views": "{ count, plural, 1 {1 entity view} other {# entity views} } izbranih" + }, + "event": { + "event-type": "Vrsta dogodka", + "type-error": "Napaka", + "type-lc-event": "Dogodek življenjskega cikla", + "type-stats": "Statistika", + "type-debug-rule-node": "Odpravljanje napak", + "type-debug-rule-chain": "Odpravljanje napak", + "no-events-prompt": "Ni dogodkov", + "error": "Napaka", + "alarm": "Alarm", + "event-time": "Čas dogodka", + "server": "Strežnik", + "body": "Vsebina", + "method": "Metoda", + "type": "Vrsta", + "entity": "Entiteta", + "message-id": "ID sporočila", + "message-type": "Vrsta sporočila", + "data-type": "Vrsta podatkov", + "relation-type": "Vrsta povezave", + "metadata": "Metapodatki", + "data": "Podatki", + "event": "Dogodek", + "status": "Stanje", + "success": "Uspeh", + "failed": "Ni uspelo", + "messages-processed": "Obdelana sporočila", + "errors-occurred": "Prišlo je do napak" + }, + "extension": { + "extensions": "Razširitve", + "selected-extensions": "{ count, plural, 1 {1 extension} other {# extensions} } izbrano", + "type": "Vrsta", + "key": "Ključ", + "value": "Vrednost", + "id": "ID", + "extension-id": "ID razširitve", + "extension-type": "Vrsta razširitve", + "transformer-json": "JSON *", + "unique-id-required": "Trenutni ID razširitve že obstaja.", + "delete": "Izbriši razširitev", + "add": "Dodaj razširitev", + "edit": "Uredi razširitev", + "delete-extension-title": "Ali ste prepričani, da želite izbrisati razširitev '{{extensionId}}'?", + "delete-extension-text": "Bodite previdni, po potrditvi podaljšanje in vsi povezani podatki ne bodo več obnovljivi.", + "delete-extensions-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 extension} other {# extensions} }?", + "delete-extensions-text": "Previdno, po potrditvi bodo odstranjene vse izbrane razširitve.", + "converters": "Pretvorniki", + "converter-id": "ID pretvornika", + "configuration": "Konfiguracija", + "converter-configurations": "Konfiguracije pretvornika", + "token": "Varnostni žeton", + "add-converter": "Dodaj pretvornik", + "add-config": "Dodaj konfiguracijo pretvornika", + "device-name-expression": "Izraz imena naprave", + "device-type-expression": "Izraz vrste naprave", + "custom": "Po meri", + "to-double": "Podvojiti", + "transformer": "Transformator", + "json-required": "Transformator json je potreben.", + "json-parse": "Ni mogoče razčleniti transformatorja json.", + "attributes": "Lastnosti", + "add-attribute": "Dodaj atribut", + "add-map": "Dodaj element preslikave", + "timeseries": "Časovne serije", + "add-timeseries": "Dodaj časovne vrste", + "field-required": "Polje je obvezno", + "brokers": "Posredniki", + "add-broker": "Dodaj posrednika", + "host": "Gostitelj", + "port": "Pristanišče", + "port-range": "Vrata naj bodo v območju od 1 do 65535.", + "ssl": "SSL", + "credentials": "Poverilnice", + "username": "Uporabniško ime", + "password": "Geslo", + "retry-interval": "Interval ponovitve v milisekundah", + "anonymous": "Anonimno", + "basic": "Osnovno", + "pem": "PEM", + "ca-cert": "Datoteka potrdila CA *", + "private-key": "Datoteka z zasebnim ključem *", + "cert": "Datoteka s potrdilom *", + "no-file": "Izbrana ni nobena datoteka.", + "drop-file": "Spustite datoteko ali kliknite, da izberete datoteko za nalaganje.", + "mapping": "Preslikava", + "topic-filter": "Glavni filter", + "converter-type": "Vrsta pretvornika", + "converter-json": "Json", + "json-name-expression": "Izraz json imena naprave", + "topic-name-expression": "Izraz teme imena naprave", + "json-type-expression": "Tip naprave json izraz", + "topic-type-expression": "Izraz teme naprave", + "attribute-key-expression": "Izraz ključnega atributa", + "attr-json-key-expression": "Izraz json ključa atributa", + "attr-topic-key-expression": "Atribut ključne besede izraza", + "request-id-expression": "Zahtevaj izraz za id", + "request-id-json-expression": "Zahtevaj izraz json id", + "request-id-topic-expression": "Zahtevaj izraz teme za id", + "response-topic-expression": "Izraz teme odziva", + "value-expression": "Vrednostni izraz", + "topic": "Tema", + "timeout": "Časovna omejitev v milisekundah", + "converter-json-required": "Potreben je pretvornik json.", + "converter-json-parse": "Ni mogoče razčleniti pretvornika json.", + "filter-expression": "Filtriraj izraz", + "connect-requests": "Zahteve za povezovanje", + "add-connect-request": "Dodaj zahtevo za povezavo", + "disconnect-requests": "Prekini zahteve", + "add-disconnect-request": "Dodaj zahtevo za prekinitev povezave", + "attribute-requests": "Zahteve za atribute", + "add-attribute-request": "Dodaj zahtevo za atribut", + "attribute-updates": "Posodobitve atributov", + "add-attribute-update": "Dodaj posodobitev atributa", + "server-side-rpc": "RPC na strežniški strani", + "add-server-side-rpc-request": "Dodaj zahtevo RPC na strani strežnika", + "device-name-filter": "Filter imena naprave", + "attribute-filter": "Filter atributov", + "method-filter": "Filter metode", + "request-topic-expression": "Zahtevaj izraz teme", + "response-timeout": "Časovna omejitev odziva v milisekundah", + "topic-expression": "Izraz teme", + "client-scope": "Obseg stranke", + "add-device": "Dodaj napravo", + "opc-server": "Strežniki", + "opc-add-server": "Dodaj strežnik", + "opc-add-server-prompt": "Prosimo, dodajte strežnik", + "opc-application-name": "Ime aplikacije", + "opc-application-uri": "Uri aplikacije", + "opc-scan-period-in-seconds": "Obdobje skeniranja v sekundah", + "opc-security": "Varnost", + "opc-identity": "Identiteta", + "opc-keystore": "Trgovina s ključi", + "opc-type": "Vrsta", + "opc-keystore-type": "Vrsta", + "opc-keystore-location": "Lokacija *", + "opc-keystore-password": "Geslo", + "opc-keystore-alias": "Vzdevek", + "opc-keystore-key-password": "Ključno geslo", + "opc-device-node-pattern": "Vzorec vozlišča naprave", + "opc-device-name-pattern": "Vzorec imena naprave", + "modbus-server": "Strežniki / podrejeni", + "modbus-add-server": "Dodaj strežnik / podrejen", + "modbus-add-server-prompt": "Prosimo, dodajte strežnik / podrejen", + "modbus-transport": "Prenos", + "modbus-tcp-reconnect": "Samodejno ponovno vzpostavi povezavo", + "modbus-rtu-over-tcp": "RTU prek TCP", + "modbus-port-name": "Ime serijskih vrat", + "modbus-encoding": "Kodiranje", + "modbus-parity": "Parnost", + "modbus-baudrate": "Hitrost prenosa", + "modbus-databits": "Podatkovni bit", + "modbus-stopbits": "Stop bits", + "modbus-databits-range": "Podatkovni bit mora biti v območju od 7 do 8.", + "modbus-stopbits-range": "Stop bitov mora biti v območju od 1 do 2.", + "modbus-unit-id": "ID enote", + "modbus-unit-id-range": "ID enote mora biti v območju od 1 do 247.", + "modbus-device-name": "Ime naprave", + "modbus-poll-period": "Obdobje ankete (ms)", + "modbus-attributes-poll-period": "Obdobje ankete atributov (ms)", + "modbus-timeseries-poll-period": "Obdobje ankete časovnih serij (ms)", + "modbus-poll-period-range": "Obdobje ankete mora imeti pozitivno vrednost.", + "modbus-tag": "Oznaka", + "modbus-function": "Funkcija", + "modbus-register-address": "Registrski naslov", + "modbus-register-address-range": "Naslov registra mora biti v območju od 0 do 65535.", + "modbus-register-bit-index": "Bitni indeks", + "modbus-register-bit-index-range": "Bitni indeks naj bo v območju od 0 do 15.", + "modbus-register-count": "Število registra", + "modbus-register-count-range": "Število registra mora biti pozitivna vrednost.", + "modbus-byte-order": "Vrstni red bajtov", + "sync": { + "status": "Stanje", + "sync": "Sinhronizacija", + "not-sync": "Ni sinhronizacija", + "last-sync-time": "Čas zadnje sinhronizacije", + "not-available": "Ni na voljo" + }, + "export-extensions-configuration": "Izvozi konfiguracijo razširitev", + "import-extensions-configuration": "Uvozi konfiguracijo razširitev", + "import-extensions": "Uvozi razširitve", + "import-extension": "Uvozi razširitev", + "export-extension": "Razširitev izvoza", + "file": "Razširitvena datoteka", + "invalid-file-error": "Neveljavna datoteka razširitve" + }, + "filter": { + "add": "Dodaj filter", + "edit": "Uredi filter", + "name": "Ime filtra", + "name-required": "Ime filtra je obvezno.", + "duplicate-filter": "Filter z istim imenom že obstaja.", + "filters": "Filtri", + "unable-delete-filter-title": "Filtra ni mogoče izbrisati", + "unable-delete-filter-text": "Filtra '{{filter}}' ni mogoče izbrisati, kot ga uporabljajo naslednji pripomočki:
{{widgetsList}}", + "duplicate-filter-error": "Najden je podvojen filter '{{filter}}'.
Filtri morajo biti na nadzorni plošči edinstveni.", + "missing-key-filters-error": "Za filter '{{filter}}' manjkajo ključni filtri.", + "filter": "Filter", + "editable": "Urejanje", + "no-filters-found": "Ni najden noben filter.", + "no-filter-text": "No filter specified", + "add-filter-prompt": "Please add filter", + "no-filter-matching": "'{{filter}}' ni mogoče najti.", + "create-new-filter": "Ustvari novega!", + "filter-required": "Potreben je filter.", + "operation": { + "operation": "Dejanje", + "equal": "enako", + "not-equal": "ni enako", + "starts-with": "začne se z", + "ends-with": "konča se z", + "contains": "vsebuje", + "not-contains": "ne vsebuje", + "greater": "večji kot", + "less": "manj kot", + "greater-or-equal": "večje ali enako", + "less-or-equal": "manjše ali enako", + "and": "in", + "or": "ali" + }, + "ignore-case": "Prezri črko", + "value": "Vrednost", + "remove-filter": "Odstrani filter", + "preview": "Predogled filtra", + "no-filters": "Ni nastavljenih filtrov", + "add-filter": "Dodaj filter", + "add-complex-filter": "Dodaj kompleksni filter", + "add-complex": "Dodaj kompleks", + "complex-filter": "Kompleksni filter", + "edit-complex-filter": "Uredi kompleksni filter", + "edit-filter-user-params": "Uredi uporabniške parametre predikata filtra", + "filter-user-params": "Filter predicate user parameters", + "user-parameters": "Uporabniški parametri", + "display-label": "Oznaka za prikaz", + "autogenerated-label": "Samodejno ustvari oznako", + "order-priority": "Prednostni vrstni red", + "key-filter": "Ključni filter", + "key-filters": "Ključni filtri", + "key-name": "Ime ključa", + "key-name-required": "Ime ključa je obvezno.", + "key-type": { + "key-type": "Tip ključa", + "attribute": "Atribut", + "timeseries": "Časovne serije", + "entity-field": "Polje entitete" + }, + "value-type": { + "value-type": "Vrsta vrednosti", + "string": "Niz", + "numeric": "Številska", + "boolean": "Logična", + "date-time": "Datum in čas" + }, + "value-type-required": "Zahtevana je vrsta vrednosti ključa.", + "key-value-type-change-title": "Ali ste prepričani, da želite spremeniti vrsto vrednosti ključa?", + "key-value-type-change-message": "Če potrdite novo vrsto vrednosti, bodo odstranjeni vsi vneseni filtri ključev.", + "no-key-filters": "Noben ključni filter ni konfiguriran", + "add-key-filter": "Dodaj ključni filter", + "remove-key-filter": "Odstrani filter ključev", + "edit-key-filter": "Uredi filter ključev", + "date": "Datum", + "time": "Čas", + "current-tenant": "Trenutni najemnik", + "current-customer": "Trenutna stranka", + "current-user": "Trenutni uporabnik", + "current-device": "Current device", + "default-value": "Privzeta vrednost", + "dynamic-source-type": "Dinamična vrsta vira", + "no-dynamic-value": "Ni dinamične vrednosti", + "source-attribute": "Izvorni atribut", + "switch-to-dynamic-value": "Preklopi na dinamično vrednost", + "switch-to-default-value": "Preklopi na privzeto vrednost" + }, + "fullscreen": { + "expand": "Razširi na celozaslonski način", + "exit": "Izhod iz celozaslonskega načina", + "toggle": "Preklopi na celozaslonski način", + "fullscreen": "Celozaslonski način" + }, + "function": { + "function": "Funkcija" + }, + "gateway": { + "add-entry": "Dodaj konfiguracijo", + "connector-add": "Dodaj nov priključek", + "connector-enabled": "Omogoči priključek", + "connector-name": "Ime priključka", + "connector-name-required": "Ime priključka je obvezno.", + "connector-type": "Vrsta priključka", + "connector-type-required": "Zahteva se vrsta priključka.", + "connectors": "Konfiguracija priključkov", + "create-new-gateway": "Ustvari nov prehod", + "create-new-gateway-text": "Ali ste prepričani, da želite ustvariti nov prehod z imenom: '{{gatewayName}}'?", + "delete": "Izbriši konfiguracijo", + "download-tip": "Prenos konfiguracijske datoteke", + "gateway": "Prehod", + "gateway-exists": "Naprava z istim imenom že obstaja.", + "gateway-name": "Ime prehoda", + "gateway-name-required": "Ime prehoda je obvezno.", + "gateway-saved": "Konfiguracija prehoda je uspešno shranjena.", + "json-parse": "Neveljaven JSON.", + "json-required": "Polje ne sme biti prazno.", + "no-connectors": "Ni priključkov", + "no-data": "Brez konfiguracij", + "no-gateway-found": "Prehod ni najden.", + "no-gateway-matching": " '{{item}}' ni mogoče najti.", + "path-logs": "Pot do dnevniških datotek", + "path-logs-required": "Pot je obvezna.", + "remote": "Oddaljena konfiguracija", + "remote-logging-level": "Raven beleženja", + "remove-entry": "Odstrani konfiguracijo", + "save-tip": "Shrani konfiguracijsko datoteko", + "security-type": "Vrsta zaščite", + "security-types": { + "access-token": "Dostopni žeton", + "tls": "TLS" + }, + "storage": "Shramba", + "storage-max-file-records": "Največ zapisov v datoteki", + "storage-max-files": "Največje število datotek", + "storage-max-files-min": "Najmanjše število je 1.", + "storage-max-files-pattern": "Številka ni veljavna.", + "storage-max-files-required": "Številka je obvezna.", + "storage-max-records": "Največ zapisov v pomnilniku", + "storage-max-records-min": "Najmanjše število zapisov je 1.", + "storage-max-records-pattern": "Številka ni veljavna.", + "storage-max-records-required": "Zahtevan je največ zapisov.", + "storage-pack-size": "Največja velikost paketa dogodkov", + "storage-pack-size-min": "Najmanjše število je 1.", + "storage-pack-size-pattern": "Številka ni veljavna.", + "storage-pack-size-required": "Zahtevana je največja velikost paketa dogodkov.", + "storage-path": "Pot pomnilnika", + "storage-path-required": "Zahtevana je pot do pomnilnika.", + "storage-type": "Vrsta pomnilnika", + "storage-types": { + "file-storage": "Shramba datotek", + "memory-storage": "Spomin pomnilnika" + }, + "thingsboard": "ThingsBoard", + "thingsboard-host": "Gostitelj ThingsBoard", + "thingsboard-host-required": "Potreben je gostitelj.", + "thingsboard-port": "Vrata ThingsBoard", + "thingsboard-port-max": "Največja številka vrat je 65535.", + "thingsboard-port-min": "Najmanjša številka vrat je 1.", + "thingsboard-port-pattern": "Vrata niso veljavna.", + "thingsboard-port-required": "Potrebna so vrata.", + "tidy": "Urejeno", + "tidy-tip": "Urejena konfiguracija JSON", + "title-connectors-json": "Konfiguracija konektorja {{typeName}}", + "tls-path-ca-certificate": "Pot do potrdila CA na prehodu", + "tls-path-client-certificate": "Pot do potrdila stranke na prehodu", + "tls-path-private-key": "Pot do zasebnega ključa na prehodu", + "toggle-fullscreen": "Preklop na celozaslonski način", + "transformer-json-config": "Konfiguracija JSON *", + "update-config": "Dodaj / posodobi konfiguracijo JSON" + }, + "grid": { + "delete-item-title": "Ali ste prepričani, da želite izbrisati ta element?", + "delete-item-text": "Bodite previdni, po potrditvi bodo ta element in vsi povezani podatki nepopravljivi.", + "delete-items-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 item} other {# items} }?", + "delete-items-action-title": "Izbriši { count, plural, 1 {1 item} other {# items} }", + "delete-items-text": "Bodite previdni, po potrditvi bodo vsi izbrani elementi odstranjeni in vsi povezani podatki nepopravljivi.", + "add-item-text": "Dodaj novo postavko", + "no-items-text": "Ni najdenih postavk", + "item-details": "Podrobnosti o postavki", + "delete-item": "Izbriši postavko", + "delete-items": "Izbriši postavke", + "scroll-to-top": "Pomakni se na vrh" + }, + "help": { + "goto-help-page": "Pojdi na stran s pomočjo" + }, + "home": { + "home": "Domov", + "profile": "Profil", + "logout": "Odjava", + "menu": "Meni", + "avatar": "Avatar", + "open-user-menu": "Odpri uporabniški meni" + }, + "import": { + "no-file": "Izbrana ni nobena datoteka", + "drop-file": "Spustite datoteko JSON ali kliknite, da izberete datoteko za nalaganje.", + "drop-file-csv": "Spustite datoteko CSV ali kliknite, da izberete datoteko za nalaganje.", + "column-value": "Vrednost", + "column-title": "Naslov", + "column-example": "Primer vrednosti podatkov", + "column-key": "Atribut / ključ telemetrije", + "csv-delimiter": "Ločilo CSV", + "csv-first-line-header": "Prva vrstica vsebuje imena stolpcev", + "csv-update-data": "Posodobi atribute / telemetrijo", + "import-csv-number-columns-error": "Datoteka mora vsebovati vsaj dva stolpca", + "import-csv-invalid-format-error": "Neveljavna oblika datoteke. Vrstica: '{{line}}'", + "column-type": { + "name": "Ime", + "type": "Vrsta", + "label": "Oznaka", + "column-type": "Vrsta stolpca", + "client-attribute": "Atribut odjemalca", + "shared-attribute": "Skupni atribut", + "server-attribute": "Atribut strežnika", + "timeseries": "Časovne serije", + "entity-field": "Polje entitete", + "access-token": "Dostopni žeton", + "isgateway": "Je prehod", + "description": "Opis" + }, + "stepper-text": { + "select-file": "Izberi datoteko", + "configuration": "Uvozi konfiguracijo", + "column-type": "Izberi vrsto stolpca", + "creat-entities": "Ustvarjanje novih entitet", + "done": "Končano" + }, + "message": { + "create-entities": "{{count}} novih entitet je bilo uspešno ustvarjenih.", + "update-entities": "{{count}} entitet je bilo uspešno posodobljenih.", + "error-entities": "Prišlo je do napake pri ustvarjanju {{count}} entitet." + } + }, + "item": { + "selected": "Izbrano" + }, + "js-func": { + "no-return-error": "Funkcija mora vrniti vrednost!", + "return-type-mismatch": "Funkcija mora vrniti vrednost vrste '{{type}}'!", + "tidy": "Urejeno", + "mini": "Mini" + }, + "key-val": { + "key": "Ključ", + "value": "Vrednost", + "remove-entry": "Odstrani vnos", + "add-entry": "Dodaj vnos", + "no-data": "Ni vnosov" + }, + "layout": { + "layout": "Postavitev", + "manage": "Upravljanje postavitev", + "settings": "Nastavitve postavitve", + "color": "Barva", + "main": "Glavni", + "right": "Pravi", + "select": "Izberi ciljno postavitev" + }, + "legend": { + "direction": "Smer legende", + "position": "Položaj legende", + "sort-legend": "Sort datakeys in legend", + "show-max": "Prikaži največjo vrednost", + "show-min": "Pokaži najmanjšo vrednost", + "show-avg": "Prikaži povprečno vrednost", + "show-total": "Prikaži skupno vrednost", + "settings": "Nastavitve legende", + "min": "najmanj", + "max": "največ", + "avg": "povprečno", + "total": "skupaj", + "comparison-time-ago": { + "days": "(pretekli dan)", + "weeks": "(pretekli teden)", + "months": "(pretekli mesec)", + "years": "(preteklo leto)" + } + }, + "login": { + "login": "Vpiši se", + "request-password-reset": "Zahtevaj ponastavitev gesla", + "reset-password": "Ponastavitev gesla", + "create-password": "Ustvari geslo", + "passwords-mismatch-error": "Vnesena gesla morajo biti enaka!", + "password-again": "Ponovi geslo", + "sign-in": "Prosimo, prijavite se", + "username": "Uporabniško ime (e-pošta)", + "remember-me": "Zapomni si me", + "forgot-password": "Ste pozabili geslo?", + "password-reset": "Resetiranje gesla", + "expired-password-reset-message": "Vaše poverilnice so potekle! Ustvarite novo geslo.", + "new-password": "Novo geslo", + "new-password-again": "Ponovi novo geslo", + "password-link-sent-message": "Povezava za ponastavitev gesla je bila uspešno poslana!", + "email": "E-naslov", + "login-with": "Prijava z {{name}}", + "or": "ali", + "error": "Napaka pri prijavi" + }, + "position": { + "top": "Zgoraj", + "bottom": "Spodaj", + "left": "Levo", + "right": "Desno" + }, + "profile": { + "profile": "Profil", + "last-login-time": "Zadnja prijava", + "change-password": "Spremeni geslo", + "current-password": "Trenutno geslo" + }, + "relation": { + "relations": "Odnosi", + "direction": "Smer", + "search-direction": { + "FROM": "Od", + "TO": "Za" + }, + "direction-type": { + "FROM": "od", + "TO": "za" + }, + "from-relations": "Odhodna razmerja", + "to-relations": "Vhodna razmerja", + "selected-relations": "{ count, plural, 1 {1 relationship} other {# Relations} } izbran", + "type": "Vrsta", + "to-entity-type": "Za vrsto entitete", + "to-entity-name": "Za ime entitete", + "from-entity-type": "Od vrste entitete", + "from-entity-name": "Od imena entitete", + "to-entity": "Za entiteto", + "from-entity": "Od entitete", + "delete": "Izbriši relacijo", + "relation-type": "Vrsta relacije", + "relation-type-required": "Vrsta relacije je obvezna.", + "any-relation-type": "Katerakoli vrsta", + "add": "Dodaj relacijo", + "edit": "Uredi relacijo", + "delete-to-relation-title": "Ali ste prepričani, da želite izbrisati relacijo z entiteto '{{entityName}}'?", + "delete-to-relation-text": "Previdno, po potrditvi entiteta '{{entityName}}' ne bo v relaciji s trenutno entiteto.", + "delete-to-relations-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 relacijo} other {# relacije} }?", + "delete-to-relations-text": "Bodite previdni, po potrditvi bodo vse izbrane relacije odstranjene in entitete ne bodo povezane med sabo.", + "delete-from-relation-title": "Ali ste prepričani, da želite izbrisati relacijo iz entitete '{{entityName}}'?", + "delete-from-relation-text": "Bodite previdni, po potrditvi trenutna entiteta ne bo v relacijii z entiteto '{{entityName}}'.", + "delete-from-relations-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 relacija} other {# odnosi} }?", + "delete-from-relations-text": "Bodite previdni, po potrditvi bodo odstranjene vse izbrane relacije in trenutna entiteta ne bo v relaciji z omenjenimi entitetami.", + "remove-relation-filter": "Odstrani relacijski filter", + "add-relation-filter": "Dodaj relacijski filter", + "any-relation": "Kakršnakoli relacija", + "relation-filters": "Relacijski filtri", + "additional-info": "Dodatne informacije (JSON)", + "invalid-additional-info": "Dodatnih informacij json ni mogoče razčleniti.", + "no-relations-text": "Ni najdenih relacij" + }, + "rulechain": { + "rulechain": "Veriga pravil", + "rulechains": "Pravila", + "root": "Izvor", + "delete": "Izbriši verigo pravil", + "name": "Ime", + "name-required": "Ime je obvezno.", + "description": "Opis", + "add": "Dodaj verigo pravil", + "set-root": "Ustvari izvor verige pravil", + "set-root-rulechain-title": "Ali ste prepričani, da želite narediti izvorno verigo pravil '{{ruleChainName}}'?", + "set-root-rulechain-text": "Po potrditvi bo veriga pravil postala izvorna in bo obravnavala vsa dohodna prometna sporočila.", + "delete-rulechain-title": "Ali ste prepričani, da želite izbrisati verigo pravil '{{ruleChainName}}'?", + "delete-rulechain-text": "Bodite previdni, po potrditvi bodo veriga pravil in vsi povezani podatki nepopravljivi.", + "delete-rulechains-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 rule chain} other {# rule chains} }?", + "delete-rulechains-action-title": "Izbriši { count, plural, 1 {1 rule chain} other {# rule chains} }", + "delete-rulechains-text": "Bodite previdni, po potrditvi bodo odstranjene vse izbrane verige pravil in vsi povezani podatki bodo postali nepopravljivi.", + "add-rulechain-text": "Dodaj novo verigo pravil", + "no-rulechains-text": "Nobena veriga pravil ni najdena", + "rulechain-details": "Podrobnosti o verigi pravil", + "details": "Podrobnosti", + "events": "Dogodki", + "system": "Sistem", + "import": "Uvozi verigo pravil", + "export": "Izvozi verigo pravil", + "export-failed-error": "Verige pravil ni mogoče izvoziti: {{error}}", + "create-new-rulechain": "Ustvari novo verigo pravil", + "rulechain-file": "Datoteka verige pravil", + "invalid-rulechain-file-error": "Ni mogoče uvoziti verige pravil: neveljavna struktura podatkov verige pravil.", + "copyId": "Kopiraj ID verige pravil", + "idCopiedMessage": "ID verige pravil je kopiran v odložišče", + "select-rulechain": "Izberi verigo pravil", + "no-rulechains-matching": "Najdena ni bila nobena veriga pravil, ki se ujema z '{{entity}}'.", + "rulechain-required": "Zahtevana je veriga pravil", + "management": "Upravljanje pravil", + "debug-mode": "Način za odpravljanje napak", + "search": "Iskanje verig pravil", + "selected-rulechains": "{ count, plural, 1 {1 rule chain} other {# rule chains} } izbrano", + "open-rulechain": "Odprta veriga pravil" + }, + "rulenode": { + "details": "Podrobnosti", + "events": "Dogodki", + "search": "Iskanje vozlišč", + "open-node-library": "Odpri knjižnico vozlišč", + "add": "Dodaj vozlišče pravila", + "name": "Ime", + "name-required": "Ime je obvezno.", + "type": "Vrsta", + "description": "Opis", + "delete": "Izbriši vozlišče pravila", + "select-all-objects": "Izberi vsa vozlišča in povezave", + "deselect-all-objects": "Prekliči izbiro vseh vozlišč in povezav", + "delete-selected-objects": "Izbriši izbrana vozlišča in povezave", + "delete-selected": "Izbriši izbrano", + "select-all": "Izberi vse", + "copy-selected": "Kopiraj izbrano", + "deselect-all": "Prekliči izbor", + "rulenode-details": "Podrobnosti vozlišča pravila", + "debug-mode": "Način za odpravljanje napak", + "configuration": "Konfiguracija", + "link": "Povezava", + "link-details": "Podrobnosti povezave vozlišča pravila", + "add-link": "Dodaj povezavo", + "link-label": "Oznaka povezave", + "link-label-required": "Oznaka povezave je obvezna.", + "custom-link-label": "Oznaka povezave po meri", + "custom-link-label-required": "Zahtevana je oznaka povezave po meri.", + "link-labels": "Oznake povezav", + "link-labels-required": "Oznake povezav so obvezne.", + "no-link-labels-found": "Oznak povezav ni bilo mogoče najti", + "no-link-label-matching": "'{{label}}' ni mogoče najti.", + "create-new-link-label": "Ustvari novega!", + "type-filter": "Filter", + "type-filter-details": "Filtriraj dohodna sporočila s konfiguriranimi pogoji", + "type-enrichment": "Obogatitev", + "type-enrichment-details": "Dodaj dodatne informacije v metapodatke sporočila", + "type-transformation": "Preobrazba", + "type-transformation-details": "Spremeni koristni tovor sporočila in metapodatke", + "type-action": "Dejanje", + "type-action-details": "Izvedite posebno dejanje", + "type-external": "Zunanji", + "type-external-details": "Interakcija z zunanjim sistemom", + "type-rule-chain": "Veriga pravil", + "type-rule-chain-details": "Posredovanje dohodnih sporočil določeni verigi pravil", + "type-input": "Vnos", + "type-input-details": "Logični vnos verige pravil, posreduje dohodna sporočila naslednjemu povezanemu vozlišču pravila", + "type-unknown": "Neznano", + "type-unknown-details": "Nerazrešeno vozlišče pravila", + "directive-is-not-loaded": "Določena konfiguracijska direktiva '{{DirectiveName}}' ni na voljo.", + "ui-resources-load-error": "Napajanje virov uporabniškega vmesnika ni uspelo.", + "invalid-target-rulechain": "Ni mogoče razrešiti ciljne verige pravil!", + "test-script-function": "Preizkusi funkcijo skripte", + "message": "Sporočilo", + "message-type": "Vrsta sporočila", + "select-message-type": "Izberi vrsto sporočila", + "message-type-required": "Zahtevana je vrsta sporočila", + "metadata": "Metapodatki", + "metadata-required": "Vnosi metapodatkov ne smejo biti prazni.", + "output": "Izdelek", + "test": "Test", + "help": "Pomoč", + "reset-debug-mode": "Ponastavi način za odpravljanje napak v vseh vozliščih" + }, + "timezone": { + "timezone": "Časovni pas", + "select-timezone": "Izberite časovni pas", + "no-timezones-matching": "Časovnih pasov, ki se ujemajo z '{{timezone}}', ni bilo mogoče najti.", + "timezone-required": "Časovni pas je obvezen." + }, + "queue": { + "select_name": "Izberi ime čakalne vrste", + "name": "Ime čakalne vrste", + "name_required": "Ime čakalne vrste je obvezno!" + }, + "tenant": { + "tenant": "Najemnik", + "tenants": "Najemniki", + "management": "Upravljanje najemnikov", + "add": "Dodaj najemnika", + "admins": "Skrbniki", + "manage-tenant-admins": "Upravljanje skrbnikov najemnikov", + "delete": "Izbriši najemnika", + "add-tenant-text": "Dodaj novega najemnika", + "no-tenants-text": "Najemnikov ni bilo mogoče najti", + "tenant-details": "Podrobnosti o najemniku", + "delete-tenant-title": "Ali ste prepričani, da želite izbrisati najemnika '{{tenantTitle}}'?", + "delete-tenant-text": "Previdno, po potrditvi bodo najemnik in vsi povezani podatki postali nepopravljivi.", + "delete-tenants-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 tenant} other {# tenants} }?", + "delete-tenants-action-title": "Izbriši { count, plural, 1 {1 tenant} other {# tenants} }", + "delete-tenants-text": "Bodite previdni, po potrditvi bodo vsi izbrani najemniki odstranjeni in vsi povezani podatki nepopravljivi.", + "title": "Naslov", + "title-required": "Naslov je obvezen.", + "description": "Opis", + "details": "Podrobnosti", + "events": "Dogodki", + "copyId": "Kopiraj ID najemnika", + "idCopiedMessage": "ID najemnika je kopiran v odložišče", + "select-tenant": "Izberi najemnika", + "no-tenants-matching": "Najden ni bil noben najemnik, ki se ujema z '{{entity}}'.", + "tenant-required": "Najemnik je obvezen", + "search": "Iskanje najemnikov", + "selected-tenants": "{ count, plural, 1 {1 tenant} other {# tenants} } izbran", + "isolated-tb-core": "Obdelava v izoliranem odlagališču ThingsBoard Core", + "isolated-tb-rule-engine": "Obdelava v izoliranem odlagališču ThingsBoard Rule Engine", + "isolated-tb-core-details": "Zahteva ločene mikro storitve na izoliranega najemnika", + "isolated-tb-rule-engine-details": "Zahteva ločene mikro storitve na izoliranega najemnika" + }, + "tenant-profile": { + "tenant-profile": "Tenant profile", + "tenant-profiles": "Tenant profiles", + "add": "Add tenant profile", + "edit": "Edit tenant profile", + "tenant-profile-details": "Tenant profile details", + "no-tenant-profiles-text": "No tenant profiles found", + "search": "Search tenant profiles", + "selected-tenant-profiles": "{ count, plural, 1 {1 tenant profile} other {# tenant profiles} } selected", + "no-tenant-profiles-matching": "No tenant profile matching '{{entity}}' were found.", + "tenant-profile-required": "Tenant profile is required", + "idCopiedMessage": "Tenant profile Id has been copied to clipboard", + "set-default": "Make tenant profile default", + "delete": "Delete tenant profile", + "copyId": "Copy tenant profile Id", + "name": "Name", + "name-required": "Name is required.", + "data": "Profile data", + "profile-configuration": "Profile configuration", + "description": "Description", + "default": "Default", + "delete-tenant-profile-title": "Are you sure you want to delete the tenant profile '{{tenantProfileName}}'?", + "delete-tenant-profile-text": "Be careful, after the confirmation the tenant profile and all related data will become unrecoverable.", + "delete-tenant-profiles-title": "Are you sure you want to delete { count, plural, 1 {1 tenant profile} other {# tenant profiles} }?", + "delete-tenant-profiles-text": "Be careful, after the confirmation all selected tenant profiles will be removed and all related data will become unrecoverable.", + "set-default-tenant-profile-title": "Are you sure you want to make the tenant profile '{{tenantProfileName}}' default?", + "set-default-tenant-profile-text": "After the confirmation the tenant profile will be marked as default and will be used for new tenants with no profile specified.", + "no-tenant-profiles-found": "No tenant profiles found.", + "create-new-tenant-profile": "Create a new one!", + "maximum-devices": "Maximum number of devices (0 - unlimited)", + "maximum-devices-required": "Maximum number of devices is required.", + "maximum-devices-range": "Minimum number of devices can't be negative", + "maximum-assets": "Maximum number of assets (0 - unlimited)", + "maximum-assets-required": "Maximum number of assets is required.", + "maximum-assets-range": "Maximum number of assets can't be negative", + "maximum-customers": "Maximum number of customers (0 - unlimited)", + "maximum-customers-required": "Maximum number of customers is required.", + "maximum-customers-range": "Maximum number of customers can't be negative", + "maximum-users": "Maximum number of users (0 - unlimited)", + "maximum-users-required": "Maximum number of users is required.", + "maximum-users-range": "Maximum number of users can't be negative", + "maximum-dashboards": "Maximum number of dashboards (0 - unlimited)", + "maximum-dashboards-required": "Maximum number of dashboards is required.", + "maximum-dashboards-range": "Maximum number of dashboards can't be negative", + "maximum-rule-chains": "Maximum number of rule chains (0 - unlimited)", + "maximum-rule-chains-required": "Maximum number of rule chains is required.", + "maximum-rule-chains-range": "Maximum number of rule chains can't be negative", + "transport-tenant-msg-rate-limit": "Transport tenant messages rate limit.", + "transport-tenant-telemetry-msg-rate-limit": "Transport tenant telemetry messages rate limit.", + "transport-tenant-telemetry-data-points-rate-limit": "Transport tenant telemetry data points rate limit.", + "transport-device-msg-rate-limit": "Transport device messages rate limit.", + "transport-device-telemetry-msg-rate-limit": "Transport device telemetry messages rate limit.", + "transport-device-telemetry-data-points-rate-limit": "Transport device telemetry data points rate limit.", + "max-transport-messages": "Maximum number of transport messages (0 - unlimited)", + "max-transport-messages-required": "Maximum number of transport messages is required.", + "max-transport-messages-range": "Maximum number of transport messages can't be negative", + "max-transport-data-points": "Maximum number of transport data points (0 - unlimited)", + "max-transport-data-points-required": "Maximum number of transport data points is required.", + "max-transport-data-points-range": "Maximum number of transport data points can't be negative", + "max-r-e-executions": "Maximum number of Rule Engine executions (0 - unlimited)", + "max-r-e-executions-required": "Maximum number of Rule Engine executions is required.", + "max-r-e-executions-range": "Maximum number of Rule Engine executions can't be negative", + "max-j-s-executions": "Maximum number of JavaScript executions (0 - unlimited)", + "max-j-s-executions-required": "Maximum number of JavaScript executions is required.", + "max-j-s-executions-range": "Maximum number of JavaScript executions can't be negative", + "max-d-p-storage-days": "Maximum number of data points storage days (0 - unlimited)", + "max-d-p-storage-days-required": "Maximum number of data points storage days is required.", + "max-d-p-storage-days-range": "Maximum number of data points storage days can't be negative", + "default-storage-ttl-days": "Default storage TTL days (0 - unlimited)", + "default-storage-ttl-days-required": "Default storage TTL days is required.", + "default-storage-ttl-days-range": "Default storage TTL days can't be negative", + "max-rule-node-executions-per-message": "Maximum number of rule node executions per message (0 - unlimited)", + "max-rule-node-executions-per-message-required": "Maximum number of rule node executions per message is required.", + "max-rule-node-executions-per-message-range": "Maximum number of rule node executions per message can't be negative", + "max-emails": "Maximum number of emails sent (0 - unlimited)", + "max-emails-required": "Maximum number of emails sent is required.", + "max-emails-range": "Maximum number of emails sent can't be negative", + "max-sms": "Maximum number of SMS sent (0 - unlimited)", + "max-sms-required": "Maximum number of SMS sent is required.", + "max-sms-range": "Maximum number of SMS sent can't be negative" + }, + "timeinterval": { + "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }", + "minutes-interval": "{ minutes, plural, 1 {1 minute} other {# minutes} }", + "hours-interval": "{ hours, plural, 1 {1 hour} other {# hours} }", + "days-interval": "{ days, plural, 1 {1 day} other {# days} }", + "days": "Dnevi", + "hours": "Ure", + "minutes": "Minute", + "seconds": "Sekunde", + "advanced": "Napredno" + }, + "timeunit": { + "seconds": "Seconds", + "minutes": "Minutes", + "hours": "Hours", + "days": "Days" + }, + "timewindow": { + "days": "{ days, plural, 1 {dan} other {# dni} }", + "hours": "{ hours, plural, 0 {hour} 1 {1 hour} other {# hours} }", + "minutes": "{ minute, plural, 0 {minute} 1 {1 minuta} other {# minut} }", + "seconds": "{ seconds, plural, 0 {second} 1 {1 second} other {# seconds} }", + "realtime": "V realnem času", + "history": "Zgodovina", + "last-prefix": "zadnji", + "period": "od {{ startTime }} do {{ endTime }}", + "edit": "Uredi časovno okno", + "date-range": "Časovno obdobje", + "last": "Zadnji", + "time-period": "Časovno obdobje", + "hide": "Skrij" + }, + "user": { + "user": "Uporabnik", + "users": "Uporabniki", + "customer-users": "Uporabniki kupcev", + "tenant-admins": "Skrbniki najemnikov", + "sys-admin": "Sistemski administrator", + "tenant-admin": "Skrbnik najemnika", + "customer": "Stranka", + "anonymous": "Anonimno", + "add": "Dodaj uporabnika", + "delete": "Izbriši uporabnika", + "add-user-text": "Dodaj novega uporabnika", + "no-users-text": "Uporabnikov ni bilo mogoče najti", + "user-details": "Podrobnosti o uporabniku", + "delete-user-title": "Ali ste prepričani, da želite izbrisati uporabnika '{{userEmail}}'?", + "delete-user-text": "Previdno, po potrditvi bodo uporabnik in vsi povezani podatki nepopravljivi.", + "delete-users-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 user} other {# users} }?", + "delete-users-action-title": "Izbriši { count, plural, 1 {1 user} other {# users} }", + "delete-users-text": "Previdno, po potrditvi bodo vsi izbrani uporabniki odstranjeni in vsi povezani podatki nepopravljivi.", + "activation-email-sent-message": "Aktivacijsko e-poštno sporočilo je bilo uspešno poslano!", + "resend-activation": "Znova pošlji aktivacijo", + "email": "E-pošta", + "email-required": "E-pošta je obvezna.", + "invalid-email-format": "Neveljavna oblika e-pošte.", + "first-name": "Ime", + "last-name": "Priimek", + "description": "Opis", + "default-dashboard": "Privzeta nadzorna plošča", + "always-fullscreen": "Vedno v celozaslonskem načinu", + "select-user": "Izberi uporabnika", + "no-users-matching": "Najti ni bilo mogoče nobenega uporabnika, ki se ujema z '{{entity}}'.", + "user-required": "Uporabnik je potreben", + "activation-method": "Način aktiviranja", + "display-activation-link": "Prikaži aktivacijsko povezavo", + "send-activation-mail": "Pošlji aktivacijsko pošto", + "activation-link": "Povezava za aktiviranje uporabnika", + "activation-link-text": "Če želite uporabnika aktivirati, uporabite naslednjo aktivacijsko povezavo :", + "copy-activation-link": "Kopiraj aktivacijsko povezavo", + "activation-link-copied-message": "Povezava za aktiviranje uporabnika je bila kopirana v odložišče", + "details": "Podrobnosti", + "login-as-tenant-admin": "Prijava kot skrbnik najemnika", + "login-as-customer-user": "Prijavi se kot uporabnik stranke", + "search": "Iskanje uporabnikov", + "selected-users": "{ count, plural, 1 {1 user} other {# users} } izbranih", + "disable-account": "Onemogoči uporabniški račun", + "enable-account": "Omogoči uporabniški račun", + "enable-account-message": "Uporabniški račun je bil uspešno omogočen!", + "disable-account-message": "Uporabniški račun je bil uspešno onemogočen!" + }, + "value": { + "type": "Vrsta vrednosti", + "string": "Niz", + "string-value": "Vrednost niza", + "string-value-required": "Vrednost niza je potrebna", + "integer": "Celota", + "integer-value": "Celotna vrednost", + "integer-value-required": "Zahtevana je celoštevilčna vrednost", + "invalid-integer-value": "Neveljavna celoštevilska vrednost", + "double": "Podvojeno", + "double-value": "Podvojena vrednost", + "double-value-required": "Zahtevana je podvojena vrednost", + "boolean": "Logično", + "boolean-value": "Logična vrednost", + "false": "Napačno", + "true": "Pravilno", + "long": "Dolgo", + "json": "JSON", + "json-value": "Vrednost JSON", + "json-value-invalid": "Vrednost JSON ima neveljavno obliko", + "json-value-required": "Vrednost JSON je potrebna." + }, + "widget": { + "widget-library": "Knjižnica pripomočkov", + "widget-bundle": "Paket pripomočkov", + "select-widgets-bundle": "Izberi paket pripomočkov", + "management": "Upravljanje pripomočkov", + "editor": "Urejevalnik pripomočkov", + "widget-type-not-found": "Težava pri nalaganju konfiguracije pripomočka.
Verjetno je bil povezan tip pripomočka odstranjen.", + "widget-type-load-error": "Pripomoček ni bil naložen zaradi naslednjih napak:", + "remove": "Odstrani pripomoček", + "edit": "Uredi pripomoček", + "remove-widget-title": "Ali ste prepričani, da želite odstraniti pripomoček '{{widgetTitle}}'?", + "remove-widget-text": "Po potrditvi bodo pripomoček in vsi povezani podatki nepopravljivi.", + "timeseries": "Časovne serije", + "search-data": "Iskanje podatkov", + "no-data-found": "Podatkov ni mogoče najti", + "latest-values": "Najnovejše vrednosti", + "rpc": "Nadzorni pripomoček", + "alarm": "Pripomoček za alarm", + "static": "Statični pripomoček", + "select-widget-type": "Izberi vrsto pripomočka", + "missing-widget-title-error": "Navesti je treba pripomoček!", + "widget-saved": "Pripomoček je shranjen", + "unable-to-save-widget-error": "Pripomočka ni mogoče shraniti! V pripomočku so napake!", + "save": "Shrani pripomoček", + "saveAs": "Shrani pripomoček kot", + "save-widget-type-as": "Shrani vrsto pripomočka kot", + "save-widget-type-as-text": "Vnesite nov naslov pripomočka in / ali izberite paket ciljnih pripomočkov", + "toggle-fullscreen": "Preklop na celozaslonski način", + "run": "Zaženi pripomoček", + "title": "Naslov pripomočka", + "title-required": "Naslov pripomočka je obvezen.", + "type": "Vrsta pripomočka", + "resources": "Viri", + "resource-url": "URL JavaScript / CSS", + "resource-is-module": "Je modul", + "remove-resource": "Odstrani vir", + "add-resource": "Dodaj vir", + "html": "HTML", + "tidy": "Urejeno", + "css": "CSS", + "settings-schema": "Shema nastavitev", + "datakey-settings-schema": "Shema nastavitev podatkovnega ključa", + "javascript": "Javascript", + "js": "JS", + "remove-widget-type-title": "Ali ste prepričani, da želite odstraniti vrsto pripomočka '{{widgetName}}'?", + "remove-widget-type-text": "Po potrditvi bodo vrsta pripomočka in vsi povezani podatki nepopravljivi.", + "remove-widget-type": "Odstrani vrsto pripomočka", + "add-widget-type": "Dodaj novo vrsto pripomočka", + "widget-type-load-failed-error": "Vrste pripomočka ni bilo mogoče naložiti!", + "widget-template-load-failed-error": "Predloge pripomočka ni bilo mogoče naložiti!", + "add": "Dodaj pripomoček", + "undo": "Razveljavi spremembe pripomočka", + "export": "Izvozi pripomoček", + "no-data": "Na pripomočku ni podatkov za prikaz", + "data-overflow": "Pripomoček prikazuje {{count}} od {{total}} entitet", + "alarm-data-overflow": "Pripomoček prikazuje alarme za {{allowedEntities}} (največ dovoljenih) entitet od {{totalEntities}} entitet" + }, + "widget-action": { + "header-button": "Gumb glave pripomočka", + "open-dashboard-state": "Pomakni se do novega stanja na nadzorni plošči", + "update-dashboard-state": "Posodobi trenutno stanje nadzorne plošče", + "open-dashboard": "Pomakni se na drugo nadzorno ploščo", + "custom": "Dejanje po meri", + "custom-pretty": "Dejanje po meri (s predlogo HTML)", + "target-dashboard-state": "Ciljno stanje nadzorne plošče", + "target-dashboard-state-required": "Ciljno stanje nadzorne plošče je potrebno", + "set-entity-from-widget": "Nastavi entiteto iz pripomočka", + "target-dashboard": "Ciljna nadzorna plošča", + "open-right-layout": "Odpri pravilno postavitev nadzorne plošče (mobilni pogled)" + }, + "widgets-bundle": { + "current": "Trenutni paketi", + "widgets-bundles": "Paketi pripomočkov", + "add": "Dodaj paket pripomočkov", + "delete": "Izbriši paket pripomočkov", + "title": "Naslov", + "title-required": "Naslov je obvezen.", + "add-widgets-bundle-text": "Dodaj nov paket pripomočkov", + "no-widgets-bundles-text": "Najden ni bil noben paket pripomočkov", + "empty": "Paket pripomočkov je prazen", + "details": "Podrobnosti", + "widgets-bundle-details": "Podrobnosti o paketu pripomočkov", + "delete-widgets-bundle-title": "Ali ste prepričani, da želite izbrisati paket pripomočkov '{{widgetsBundleTitle}}'?", + "delete-widgets-bundle-text": "Bodite previdni, po potrditvi bodo pripomočki in vsi povezani podatki postali nepopravljivi.", + "delete-widgets-bundles-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 widgets bundle} other {# widgets bundles} }?", + "delete-widgets-bundles-action-title": "Izbriši { count, plural, 1 {1 widgets bundle} other {# widgets bundles} }", + "delete-widgets-bundles-text": "Bodite previdni, po potrditvi bodo odstranjeni vsi paketi pripomočkov, vsi povezani podatki pa bodo postali nepopravljivi.", + "no-widgets-bundles-matching": "Najden ni bil noben paket pripomočkov, ki se ujema z '{{widgetsBundle}}'.", + "widgets-bundle-required": "Paket pripomočkov je potreben.", + "system": "Sistem", + "import": "Uvozi paket pripomočkov", + "export": "Izvozi paket pripomočkov", + "export-failed-error": "Ni mogoče izvoziti paketa pripomočkov: {{error}}", + "create-new-widgets-bundle": "Ustvari nov paket pripomočkov", + "widgets-bundle-file": "Datoteka paketa pripomočkov", + "invalid-widgets-bundle-file-error": "Ni mogoče uvoziti paketa pripomočkov: neveljavna podatkovna struktura paketa pripomočkov.", + "search": "Iskanje po paketih pripomočkov", + "selected-widgets-bundles": "{ count, plural, 1 {1 widgets bundle} other {# widgets bundles} } izbranih", + "open-widgets-bundle": "Odpri paket pripomočkov" + }, + "widget-config": { + "data": "Podatki", + "settings": "Nastavitve", + "advanced": "Napredno", + "title": "Naslov", + "title-tooltip": "Opis naslova", + "general-settings": "Splošne nastavitve", + "display-title": "Prikaži naslov", + "drop-shadow": "Spusti senco", + "enable-fullscreen": "Omogoči celozaslonski način", + "background-color": "Barva ozadja", + "text-color": "Barva besedila", + "padding": "Oblazinjenje", + "margin": "Stopnja", + "widget-style": "Slog pripomočkov", + "title-style": "Slog naslova", + "mobile-mode-settings": "Nastavitve mobilnega načina", + "order": "Naročilo", + "height": "Višina", + "units": "Poseben simbol za prikaz poleg vrednosti", + "decimals": "Število številk po plavajoči vejici", + "timewindow": "Časovno okno", + "use-dashboard-timewindow": "Uporabi časovno okno nadzorne plošče", + "display-timewindow": "Prikaži časovno okno", + "display-legend": "Prikaži legendo", + "datasources": "Viri podatkov", + "maximum-datasources": "Največ { count, plural, 1 {1 vir podatkov je dovoljen.} other {# vir podatkov je dovoljen} }", + "datasource-type": "Vrsta", + "datasource-parameters": "Parametri", + "remove-datasource": "Odstrani vir podatkov", + "add-datasource": "Dodaj vir podatkov", + "target-device": "Ciljna naprava", + "alarm-source": "Vir alarma", + "actions": "Dejanja", + "action": "Dejanje", + "add-action": "Dodaj dejanje", + "search-actions": "Iskanje dejanj", + "no-actions-text": "Ni najdenih dejanj", + "action-source": "Vir dejanja", + "action-source-required": "Zahtevan je vir dejanj.", + "action-name": "Ime", + "action-name-required": "Ime dejanja je obvezno.", + "action-name-not-unique": "Še eno dejanje z istim imenom že obstaja.
Ime dejanja mora biti enolično v istem viru dejanj.", + "action-icon": "Ikona", + "action-type": "Vrsta", + "action-type-required": "Zahtevana je vrsta dejanja.", + "edit-action": "Uredi dejanje", + "delete-action": "Izbriši dejanje", + "delete-action-title": "Izbriši dejanje pripomočka", + "delete-action-text": "Ali ste prepričani, da želite izbrisati dejanje pripomočka z imenom '{{actionName}}'?", + "display-icon": "Prikaži ikono naslova", + "icon-color": "Barva ikone", + "icon-size": "Velikost ikone" + }, + "widget-type": { + "import": "Uvozi vrsto pripomočka", + "export": "Izvozi vrsto pripomočka", + "export-failed-error": "Ni mogoče izvoziti vrste pripomočka: {{error}}", + "create-new-widget-type": "Ustvari novo vrsto pripomočka", + "widget-type-file": "Datoteka vrste pripomočka", + "invalid-widget-type-file-error": "Ne morem uvoziti vrste gradnika: Neveljavna struktura podatkov vrste pripomočka." + }, + "widgets": { + "date-range-navigator": { + "localizationMap": { + "Sun": "Ned", + "Mon": "Pon", + "Tue": "Tor", + "Wed": "Sre", + "Thu": "Čet", + "Fri": "Pet", + "Sat": "Sob", + "Jan": "Jan", + "Feb": "Feb", + "Mar": "Mar", + "Apr": "Apr", + "May": "Maj", + "Jun": "Jun", + "Jul": "Jul", + "Aug": "Avg", + "Sep": "Sep", + "Oct": "Okt", + "Nov": "Nov", + "Dec": "Dec", + "January": "Januar", + "February": "Februar", + "March": "Marec", + "April": "April", + "June": "Junij", + "July": "Julij", + "August": "Avgust", + "September": "September", + "October": "Oktober", + "November": "November", + "December": "December", + "Custom Date Range": "Časovno obdobje po meri", + "Date Range Template": "Predloga časovnega obdobja", + "Today": "Danes", + "Yesterday": "Včeraj", + "This Week": "Ta teden", + "Last Week": "Prejšnji teden", + "This Month": "Ta mesec", + "Last Month": "Prejšnji mesec", + "Year": "Leto", + "This Year": "To leto", + "Last Year": "Lansko leto", + "Date picker": "Izbirnik datuma", + "Hour": "Ura", + "Day": "Dan", + "Week": "Teden", + "2 weeks": "2 tedna", + "Month": "Mesec", + "3 months": "3 mesece", + "6 months": "6 mesecev", + "Custom interval": "Interval po meri", + "Interval": "Interval", + "Step size": "Velikost koraka", + "Ok": "V redu" + } + }, + "input-widgets": { + "attribute-not-allowed": "Parametra atributa v tem pripomočku ni mogoče uporabiti", + "blocked-location": "Geolokacija je blokirana v vašem brskalniku", + "claim-device": "Zahtevaj napravo", + "claim-failed": "Naprave ni bilo mogoče zahtevati!", + "claim-not-found": "Naprave ni mogoče najti!", + "claim-successful": "Naprava je bila uspešno zahtevana!", + "date": "Datum", + "device-name": "Ime naprave", + "device-name-required": "Ime naprave je obvezno", + "discard-changes": "Zavrzi spremembe", + "entity-attribute-required": "Zahtevan je atribut entitete", + "entity-coordinate-required": "Oba polja, zemljepisna širina in dolžina sta obvezna", + "entity-timeseries-required": "Potreben je časovni niz entitet", + "get-location": "Pridobi trenutno lokacijo", + "invalid-date": "Invalid Date", + "latitude": "Zemljepisna širina", + "longitude": "Zemljepisna dolžina", + "min-value-error": "Min value is {{value}}", + "max-value-error": "Max value is {{value}}", + "not-allowed-entity": "Izbrana entiteta ne sme imeti atributov v skupni rabi", + "no-attribute-selected": "Noben atribut ni izbran", + "no-datakey-selected": "Nobena podatkovna tipka ni izbrana", + "no-coordinate-specified": "Podatkovni ključ za zemljepisno širino / dolžino ni določen", + "no-entity-selected": "Nobena entiteta ni izbrana", + "no-image": "Ni slike", + "no-support-geolocation": "Vaš brskalnik ne podpira geolokacije", + "no-support-web-camera": "Ni podprte spletne kamere", + "enable-https-use-widget": "Please enable HTTPS to use this widget", + "no-found-your-camera": "Can't find your camera", + "no-permission-camera": "Permission was denied by the user / This site doesn't have permission to use the camera", + "no-timeseries-selected": "Izbrana ni nobena časovna serija", + "secret-key": "Skrivni ključ", + "secret-key-required": "Potreben je skrivni ključ", + "switch-attribute-value": "Preklopi vrednost atributa entitete", + "switch-camera": "Preklopi kamero", + "switch-timeseries-value": "Preklopi vrednost časovnega niza entitete", + "take-photo": "Fotografiraj", + "time": "Čas", + "timeseries-not-allowed": "V tem pripomočku ni mogoče uporabiti parametra časovne vrste", + "update-failed": "Posodobitev ni uspela", + "update-successful": "Posodobitev uspešna", + "update-attribute": "Posodobi atribut", + "update-timeseries": "Posodobi časovne vrste", + "value": "Vrednost" + } + }, + "icon": { + "icon": "Ikona", + "select-icon": "Izberi ikono", + "material-icons": "Ikone materiala", + "show-all": "Pokaži vse ikone" + }, + "custom": { + "widget-action": { + "action-cell-button": "Gumb akcijske celice", + "row-click": "Klik na vrstico", + "polygon-click": "Klik na poligon", + "marker-click": "Klik na oznako", + "tooltip-tag-action": "Dejanje oznake orodja", + "node-selected": "Na izbranem vozlišču", + "element-click": "Klik na HTML element", + "pie-slice-click": "Klik na rezino", + "row-double-click": "Dvojni klik na vrstico" + } + }, + "language": { + "language": "Jezik" + } +} diff --git a/ui-ngx/src/index.html b/ui-ngx/src/index.html index af49c0c938..e05c3b7f34 100644 --- a/ui-ngx/src/index.html +++ b/ui-ngx/src/index.html @@ -16,7 +16,7 @@ --> - + ThingsBoard @@ -24,8 +24,84 @@ + + + +
+
+
+
+
diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index 190fbb1ba7..066559d1fd 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -15,7 +15,6 @@ */ /* You can add global styles to this file, and also import other style files */ -@import '~material-design-icons/iconfont/material-icons.css'; @import '~typeface-roboto/index.css'; @import '~font-awesome/css/font-awesome.min.css'; @import 'theme.scss'; @@ -41,7 +40,7 @@ body, html { body { margin: 0; padding: 0; - background-color: rgb(250,250,250); + background-color: #eee; overflow: hidden; }