diff --git a/application/src/main/data/json/demo/dashboards/firmware.json b/application/src/main/data/json/demo/dashboards/firmware.json index 779f2cb93c..7a21e86761 100644 --- a/application/src/main/data/json/demo/dashboards/firmware.json +++ b/application/src/main/data/json/demo/dashboards/firmware.json @@ -1,24 +1,34 @@ { "title": "Firmware", "image": null, + "mobileHide": false, + "mobileOrder": null, "configuration": { "description": "", "widgets": { "cd03188e-cd9d-9601-fd57-da4cb95fc016": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "entities_table", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 7.5, "sizeY": 6.5, "config": { "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 86400000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, "interval": 1000, - "timewindowMs": 86400000 + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694085363133, + "endTimeMs": 1694171763133 + }, + "quickInterval": "CURRENT_DAY" }, "aggregation": { "type": "NONE", @@ -299,16 +309,11 @@ }, "row": 0, "col": 0, - "id": "cd03188e-cd9d-9601-fd57-da4cb95fc016" + "id": "cd03188e-cd9d-9601-fd57-da4cb95fc016", + "typeFullFqn": "system.cards.entities_table" }, "100b756c-0082-6505-3ae1-3603e6deea48": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "timeseries_table", "type": "timeseries", - "title": "New widget", - "image": null, - "description": null, "sizeX": 8, "sizeY": 6.5, "config": { @@ -461,14 +466,11 @@ }, "row": 0, "col": 0, - "id": "100b756c-0082-6505-3ae1-3603e6deea48" + "id": "100b756c-0082-6505-3ae1-3603e6deea48", + "typeFullFqn": "system.cards.timeseries_table" }, "17543c57-af4a-2c1e-bf12-53a7b46791e6": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "html_value_card", "type": "latest", - "title": "New widget", "sizeX": 8, "sizeY": 3, "config": { @@ -496,8 +498,27 @@ } ], "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { - "timewindowMs": 60000 + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694085363133, + "endTimeMs": 1694171763133 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 } }, "showTitle": false, @@ -545,14 +566,11 @@ "enableDataExport": false, "displayTimewindow": true }, - "id": "17543c57-af4a-2c1e-bf12-53a7b46791e6" + "id": "17543c57-af4a-2c1e-bf12-53a7b46791e6", + "typeFullFqn": "system.cards.html_value_card" }, "6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "html_value_card", "type": "latest", - "title": "New widget", "sizeX": 8, "sizeY": 3, "config": { @@ -580,8 +598,27 @@ } ], "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { - "timewindowMs": 60000 + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694085363133, + "endTimeMs": 1694171763133 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 } }, "showTitle": false, @@ -629,14 +666,11 @@ "enableDataExport": false, "displayTimewindow": true }, - "id": "6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6" + "id": "6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6", + "typeFullFqn": "system.cards.html_value_card" }, "e6674227-9cf3-a2f6-ecac-5ccfc38a3c81": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "html_value_card", "type": "latest", - "title": "New widget", "sizeX": 8, "sizeY": 3, "config": { @@ -664,8 +698,27 @@ } ], "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { - "timewindowMs": 60000 + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694085363133, + "endTimeMs": 1694171763133 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 } }, "showTitle": false, @@ -713,14 +766,11 @@ "enableDataExport": false, "displayTimewindow": true }, - "id": "e6674227-9cf3-a2f6-ecac-5ccfc38a3c81" + "id": "e6674227-9cf3-a2f6-ecac-5ccfc38a3c81", + "typeFullFqn": "system.cards.html_value_card" }, "77b10144-b904-edd5-8c7c-8fb75616c6d8": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "html_value_card", "type": "latest", - "title": "New widget", "sizeX": 8, "sizeY": 3, "config": { @@ -748,8 +798,27 @@ } ], "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { - "timewindowMs": 60000 + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694085363133, + "endTimeMs": 1694171763133 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 } }, "showTitle": false, @@ -797,23 +866,32 @@ "enableDataExport": false, "displayTimewindow": true }, - "id": "77b10144-b904-edd5-8c7c-8fb75616c6d8" + "id": "77b10144-b904-edd5-8c7c-8fb75616c6d8", + "typeFullFqn": "system.cards.html_value_card" }, "21be08bb-ec90-f760-ad6f-e7678f12c401": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "entities_table", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 7.5, "sizeY": 6.5, "config": { "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { + "realtimeType": 1, "interval": 1000, - "timewindowMs": 86400000 + "timewindowMs": 86400000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694085363133, + "endTimeMs": 1694171763133 + }, + "quickInterval": "CURRENT_DAY" }, "aggregation": { "type": "NONE", @@ -1094,23 +1172,32 @@ }, "row": 0, "col": 0, - "id": "21be08bb-ec90-f760-ad6f-e7678f12c401" + "id": "21be08bb-ec90-f760-ad6f-e7678f12c401", + "typeFullFqn": "system.cards.entities_table" }, "e8280043-d3dc-7acb-c2ff-a4522972ff91": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "entities_table", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 7.5, "sizeY": 6.5, "config": { "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 86400000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, "interval": 1000, - "timewindowMs": 86400000 + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694085363133, + "endTimeMs": 1694171763133 + }, + "quickInterval": "CURRENT_DAY" }, "aggregation": { "type": "NONE", @@ -1391,23 +1478,32 @@ }, "row": 0, "col": 0, - "id": "e8280043-d3dc-7acb-c2ff-a4522972ff91" + "id": "e8280043-d3dc-7acb-c2ff-a4522972ff91", + "typeFullFqn": "system.cards.entities_table" }, "3624013b-378c-f110-5eba-ae95c25a4dcc": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "entities_table", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 7.5, "sizeY": 6.5, "config": { "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 86400000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, "interval": 1000, - "timewindowMs": 86400000 + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694085363133, + "endTimeMs": 1694171763133 + }, + "quickInterval": "CURRENT_DAY" }, "aggregation": { "type": "NONE", @@ -1688,23 +1784,32 @@ }, "row": 0, "col": 0, - "id": "3624013b-378c-f110-5eba-ae95c25a4dcc" + "id": "3624013b-378c-f110-5eba-ae95c25a4dcc", + "typeFullFqn": "system.cards.entities_table" }, "d2d13e0d-4e71-889f-9343-ad2f0af9f176": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "entities_table", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 7.5, "sizeY": 6.5, "config": { "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { + "realtimeType": 1, "interval": 1000, - "timewindowMs": 86400000 + "timewindowMs": 86400000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694085363133, + "endTimeMs": 1694171763133 + }, + "quickInterval": "CURRENT_DAY" }, "aggregation": { "type": "NONE", @@ -1985,7 +2090,8 @@ }, "row": 0, "col": 0, - "id": "d2d13e0d-4e71-889f-9343-ad2f0af9f176" + "id": "d2d13e0d-4e71-889f-9343-ad2f0af9f176", + "typeFullFqn": "system.cards.entities_table" } }, "states": { @@ -2035,7 +2141,8 @@ "autoFillHeight": true, "backgroundImageUrl": null, "mobileAutoFillHeight": true, - "mobileRowHeight": 70 + "mobileRowHeight": 70, + "outerMargin": true } } } @@ -2062,7 +2169,8 @@ "autoFillHeight": true, "backgroundImageUrl": null, "mobileAutoFillHeight": false, - "mobileRowHeight": 70 + "mobileRowHeight": 70, + "outerMargin": true } } } @@ -2089,7 +2197,8 @@ "autoFillHeight": true, "backgroundImageUrl": null, "mobileAutoFillHeight": false, - "mobileRowHeight": 70 + "mobileRowHeight": 70, + "outerMargin": true } } } @@ -2116,7 +2225,8 @@ "autoFillHeight": true, "backgroundImageUrl": null, "mobileAutoFillHeight": false, - "mobileRowHeight": 70 + "mobileRowHeight": 70, + "outerMargin": true } } } @@ -2143,7 +2253,8 @@ "autoFillHeight": true, "backgroundImageUrl": null, "mobileAutoFillHeight": false, - "mobileRowHeight": 70 + "mobileRowHeight": 70, + "outerMargin": true } } } @@ -2170,7 +2281,8 @@ "autoFillHeight": true, "backgroundImageUrl": null, "mobileAutoFillHeight": false, - "mobileRowHeight": 70 + "mobileRowHeight": 70, + "outerMargin": true } } } @@ -2488,5 +2600,6 @@ "showUpdateDashboardImage": false } }, + "externalId": null, "name": "Firmware" } \ No newline at end of file diff --git a/application/src/main/data/json/demo/dashboards/gateways.json b/application/src/main/data/json/demo/dashboards/gateways.json index 6051fbd514..f4eb275c3c 100644 --- a/application/src/main/data/json/demo/dashboards/gateways.json +++ b/application/src/main/data/json/demo/dashboards/gateways.json @@ -1,20 +1,33 @@ { "title": "Gateways", + "image": null, + "mobileHide": false, + "mobileOrder": null, "configuration": { "widgets": { "94715984-ae74-76e4-20b7-2f956b01ed80": { - "isSystemType": true, - "bundleAlias": "entity_admin_widgets", - "typeAlias": "device_admin_table", "type": "latest", - "title": "New widget", "sizeX": 24, "sizeY": 12, "config": { "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { + "realtimeType": 1, "interval": 1000, - "timewindowMs": 86400000 + "timewindowMs": 86400000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694085270425, + "endTimeMs": 1694171670425 + }, + "quickInterval": "CURRENT_DAY" }, "aggregation": { "type": "NONE", @@ -187,14 +200,11 @@ ] } }, - "id": "94715984-ae74-76e4-20b7-2f956b01ed80" + "id": "94715984-ae74-76e4-20b7-2f956b01ed80", + "typeFullFqn": "system.entity_admin_widgets.device_admin_table" }, "eadabbc7-519e-76fc-ba10-b3fe8c18da10": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "timeseries_table", "type": "timeseries", - "title": "New widget", "sizeX": 14, "sizeY": 13, "config": { @@ -255,14 +265,11 @@ "titleTooltip": "", "displayTimewindow": true }, - "id": "eadabbc7-519e-76fc-ba10-b3fe8c18da10" + "id": "eadabbc7-519e-76fc-ba10-b3fe8c18da10", + "typeFullFqn": "system.cards.timeseries_table" }, "f928afc4-30d1-8d0c-e3cf-777f9f9d1155": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "basic_timeseries", "type": "timeseries", - "title": "New widget", "sizeX": 17, "sizeY": 4, "config": { @@ -375,6 +382,15 @@ "xaxisSecond": { "axisPosition": "top", "showLabels": true + }, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "right", + "showMin": true, + "showMax": true, + "showAvg": true, + "showTotal": true } }, "title": "Real time information", @@ -393,25 +409,13 @@ "widgetStyle": {}, "useDashboardTimewindow": false, "displayTimewindow": true, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "right", - "showMin": true, - "showMax": true, - "showAvg": true, - "showTotal": true - }, "actions": {} }, - "id": "f928afc4-30d1-8d0c-e3cf-777f9f9d1155" + "id": "f928afc4-30d1-8d0c-e3cf-777f9f9d1155", + "typeFullFqn": "system.charts.basic_timeseries" }, "2a95b473-042d-59d0-2da2-40d0cccb6c8a": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "timeseries_table", "type": "timeseries", - "title": "New widget", "sizeX": 7, "sizeY": 7, "config": { @@ -491,14 +495,11 @@ "showTotal": false } }, - "id": "2a95b473-042d-59d0-2da2-40d0cccb6c8a" + "id": "2a95b473-042d-59d0-2da2-40d0cccb6c8a", + "typeFullFqn": "system.cards.timeseries_table" }, "aaa69366-aacc-9028-65aa-645c0f8533ec": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "basic_timeseries", "type": "timeseries", - "title": "New widget", "sizeX": 17, "sizeY": 4, "config": { @@ -604,6 +605,15 @@ "xaxisSecond": { "axisPosition": "top", "showLabels": true + }, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "right", + "showMin": true, + "showMax": true, + "showAvg": true, + "showTotal": true } }, "title": "History information", @@ -622,25 +632,13 @@ "widgetStyle": {}, "useDashboardTimewindow": true, "displayTimewindow": true, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "right", - "showMin": true, - "showMax": true, - "showAvg": true, - "showTotal": true - }, "actions": {} }, - "id": "aaa69366-aacc-9028-65aa-645c0f8533ec" + "id": "aaa69366-aacc-9028-65aa-645c0f8533ec", + "typeFullFqn": "system.charts.basic_timeseries" }, "ce5c7d01-a3ef-5cf0-4578-8505135c23a0": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "basic_timeseries", "type": "timeseries", - "title": "New widget", "sizeX": 17, "sizeY": 4, "config": { @@ -751,6 +749,15 @@ "xaxisSecond": { "axisPosition": "top", "showLabels": true + }, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "right", + "showMin": true, + "showMax": true, + "showAvg": true, + "showTotal": true } }, "title": "Real time information", @@ -769,25 +776,13 @@ "widgetStyle": {}, "useDashboardTimewindow": false, "displayTimewindow": true, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "right", - "showMin": true, - "showMax": true, - "showAvg": true, - "showTotal": true - }, "actions": {} }, - "id": "ce5c7d01-a3ef-5cf0-4578-8505135c23a0" + "id": "ce5c7d01-a3ef-5cf0-4578-8505135c23a0", + "typeFullFqn": "system.charts.basic_timeseries" }, "466f046d-6005-a168-b107-60fcb2469cd5": { - "isSystemType": true, - "bundleAlias": "gateway_widgets", - "typeAlias": "attributes_card", "type": "latest", - "title": "New widget", "sizeX": 7, "sizeY": 5, "config": { @@ -799,8 +794,27 @@ } ], "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { - "timewindowMs": 60000 + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694085270425, + "endTimeMs": 1694171670425 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 } }, "showTitle": true, @@ -832,14 +846,11 @@ "showLegend": false, "actions": {} }, - "id": "466f046d-6005-a168-b107-60fcb2469cd5" + "id": "466f046d-6005-a168-b107-60fcb2469cd5", + "typeFullFqn": "system.gateway_widgets.attributes_card" }, "8fc32225-164f-3258-73f7-e6b6d959cf0b": { - "isSystemType": true, - "bundleAlias": "gateway_widgets", - "typeAlias": "config_form_latest", "type": "latest", - "title": "New widget", "sizeX": 10, "sizeY": 9, "config": { @@ -851,8 +862,27 @@ } ], "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { - "timewindowMs": 60000 + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694085270425, + "endTimeMs": 1694171670425 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 } }, "showTitle": true, @@ -881,14 +911,11 @@ "showLegend": false, "actions": {} }, - "id": "8fc32225-164f-3258-73f7-e6b6d959cf0b" + "id": "8fc32225-164f-3258-73f7-e6b6d959cf0b", + "typeFullFqn": "system.gateway_widgets.config_form_latest" }, "063fc179-c9fd-f952-e714-f24e9c43c05c": { - "isSystemType": true, - "bundleAlias": "control_widgets", - "typeAlias": "rpcbutton", "type": "rpc", - "title": "New widget", "sizeX": 4, "sizeY": 2, "config": { @@ -930,14 +957,11 @@ "b2487e75-2fa4-f211-142c-434dfd50c70c" ] }, - "id": "063fc179-c9fd-f952-e714-f24e9c43c05c" + "id": "063fc179-c9fd-f952-e714-f24e9c43c05c", + "typeFullFqn": "system.control_widgets.rpcbutton" }, "3c2134cc-27a0-93e1-dbe1-2fa7c1ce16b7": { - "isSystemType": true, - "bundleAlias": "control_widgets", - "typeAlias": "rpcbutton", "type": "rpc", - "title": "New widget", "sizeX": 4, "sizeY": 2, "config": { @@ -979,14 +1003,11 @@ "b2487e75-2fa4-f211-142c-434dfd50c70c" ] }, - "id": "3c2134cc-27a0-93e1-dbe1-2fa7c1ce16b7" + "id": "3c2134cc-27a0-93e1-dbe1-2fa7c1ce16b7", + "typeFullFqn": "system.control_widgets.rpcbutton" }, "6770b6ba-eff8-df05-75f8-c1f9326d4842": { - "isSystemType": true, - "bundleAlias": "input_widgets", - "typeAlias": "markers_placement_openstreetmap", "type": "latest", - "title": "New widget", "sizeX": 6, "sizeY": 4, "config": { @@ -1015,8 +1036,27 @@ } ], "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { - "timewindowMs": 60000 + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694085270425, + "endTimeMs": 1694171670425 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 } }, "showTitle": false, @@ -1083,7 +1123,8 @@ "titleTooltip": "", "displayTimewindow": true }, - "id": "6770b6ba-eff8-df05-75f8-c1f9326d4842" + "id": "6770b6ba-eff8-df05-75f8-c1f9326d4842", + "typeFullFqn": "system.input_widgets.markers_placement_openstreetmap" } }, "states": { @@ -1108,7 +1149,8 @@ "autoFillHeight": true, "mobileAutoFillHeight": false, "mobileRowHeight": 70, - "margin": 10 + "margin": 10, + "outerMargin": true } } } @@ -1158,7 +1200,8 @@ "autoFillHeight": true, "mobileAutoFillHeight": false, "mobileRowHeight": 70, - "margin": 10 + "margin": 10, + "outerMargin": true } } } @@ -1211,7 +1254,8 @@ "autoFillHeight": true, "mobileAutoFillHeight": true, "mobileRowHeight": 70, - "margin": 10 + "margin": 10, + "outerMargin": true } } } @@ -1224,8 +1268,10 @@ "filter": { "type": "deviceType", "resolveMultiple": true, - "deviceType": "gateway", - "deviceNameFilter": "" + "deviceNameFilter": "", + "deviceTypes": [ + "gateway" + ] } }, "b2487e75-2fa4-f211-142c-434dfd50c70c": { @@ -1261,7 +1307,9 @@ "showDashboardExport": true, "toolbarAlwaysOpen": true, "titleColor": "rgba(0,0,0,0.870588)" - } + }, + "filters": {} }, + "externalId": null, "name": "Gateways" -} +} \ No newline at end of file diff --git a/application/src/main/data/json/demo/dashboards/rule_engine_statistics.json b/application/src/main/data/json/demo/dashboards/rule_engine_statistics.json index b8231ab880..c84faa359e 100644 --- a/application/src/main/data/json/demo/dashboards/rule_engine_statistics.json +++ b/application/src/main/data/json/demo/dashboards/rule_engine_statistics.json @@ -1,13 +1,12 @@ { "title": "Rule Engine Statistics", + "image": null, + "mobileHide": false, + "mobileOrder": null, "configuration": { "widgets": { "81987f19-3eac-e4ce-b790-d96e9b54d9a0": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "basic_timeseries", "type": "timeseries", - "title": "New widget", "sizeX": 12, "sizeY": 7, "config": { @@ -151,6 +150,15 @@ "xaxisSecond": { "axisPosition": "top", "showLabels": true + }, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "showMin": true, + "showMax": true, + "showAvg": false, + "showTotal": true } }, "title": "Queue Stats", @@ -169,25 +177,13 @@ "widgetStyle": {}, "useDashboardTimewindow": false, "displayTimewindow": true, - "showLegend": true, - "actions": {}, - "legendConfig": { - "direction": "column", - "position": "bottom", - "showMin": true, - "showMax": true, - "showAvg": false, - "showTotal": true - } + "actions": {} }, - "id": "81987f19-3eac-e4ce-b790-d96e9b54d9a0" + "id": "81987f19-3eac-e4ce-b790-d96e9b54d9a0", + "typeFullFqn": "system.charts.basic_timeseries" }, "5eb79712-5c24-3060-7e4f-6af36b8f842d": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "timeseries_table", "type": "timeseries", - "title": "New widget", "sizeX": 24, "sizeY": 5, "config": { @@ -272,14 +268,11 @@ "titleTooltip": "", "displayTimewindow": true }, - "id": "5eb79712-5c24-3060-7e4f-6af36b8f842d" + "id": "5eb79712-5c24-3060-7e4f-6af36b8f842d", + "typeFullFqn": "system.cards.timeseries_table" }, "ad3f1417-87a8-750e-fc67-49a2de1466d4": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "basic_timeseries", "type": "timeseries", - "title": "New widget", "sizeX": 12, "sizeY": 7, "config": { @@ -393,6 +386,15 @@ "xaxisSecond": { "axisPosition": "top", "showLabels": true + }, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "showMin": true, + "showMax": true, + "showAvg": false, + "showTotal": true } }, "title": "Processing Failures and Timeouts", @@ -411,18 +413,10 @@ "widgetStyle": {}, "useDashboardTimewindow": false, "displayTimewindow": true, - "showLegend": true, - "actions": {}, - "legendConfig": { - "direction": "column", - "position": "bottom", - "showMin": true, - "showMax": true, - "showAvg": false, - "showTotal": true - } + "actions": {} }, - "id": "ad3f1417-87a8-750e-fc67-49a2de1466d4" + "id": "ad3f1417-87a8-750e-fc67-49a2de1466d4", + "typeFullFqn": "system.charts.basic_timeseries" } }, "states": { @@ -457,14 +451,12 @@ "backgroundColor": "#eeeeee", "color": "rgba(0,0,0,0.870588)", "columns": 24, - "margins": [ - 10, - 10 - ], "backgroundSizeMode": "100%", "autoFillHeight": true, "mobileAutoFillHeight": false, - "mobileRowHeight": 70 + "mobileRowHeight": 70, + "margin": 10, + "outerMargin": true } } } @@ -477,8 +469,10 @@ "filter": { "type": "assetType", "resolveMultiple": true, - "assetType": "TbServiceQueue", - "assetNameFilter": "" + "assetNameFilter": "", + "assetTypes": [ + "TbServiceQueue" + ] } } }, @@ -514,7 +508,9 @@ "showDashboardTimewindow": true, "showDashboardExport": true, "toolbarAlwaysOpen": true - } + }, + "filters": {} }, + "externalId": null, "name": "Rule Engine Statistics" } \ No newline at end of file diff --git a/application/src/main/data/json/demo/dashboards/software.json b/application/src/main/data/json/demo/dashboards/software.json index e9d7e9bbbd..eea81e5368 100644 --- a/application/src/main/data/json/demo/dashboards/software.json +++ b/application/src/main/data/json/demo/dashboards/software.json @@ -1,24 +1,34 @@ { "title": "Software", "image": null, + "mobileHide": false, + "mobileOrder": null, "configuration": { "description": "", "widgets": { "cd03188e-cd9d-9601-fd57-da4cb95fc016": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "entities_table", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 7.5, "sizeY": 6.5, "config": { "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 86400000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, "interval": 1000, - "timewindowMs": 86400000 + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694085343279, + "endTimeMs": 1694171743279 + }, + "quickInterval": "CURRENT_DAY" }, "aggregation": { "type": "NONE", @@ -299,16 +309,11 @@ }, "row": 0, "col": 0, - "id": "cd03188e-cd9d-9601-fd57-da4cb95fc016" + "id": "cd03188e-cd9d-9601-fd57-da4cb95fc016", + "typeFullFqn": "system.cards.entities_table" }, "100b756c-0082-6505-3ae1-3603e6deea48": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "timeseries_table", "type": "timeseries", - "title": "New widget", - "image": null, - "description": null, "sizeX": 8, "sizeY": 6.5, "config": { @@ -461,14 +466,11 @@ }, "row": 0, "col": 0, - "id": "100b756c-0082-6505-3ae1-3603e6deea48" + "id": "100b756c-0082-6505-3ae1-3603e6deea48", + "typeFullFqn": "system.cards.timeseries_table" }, "17543c57-af4a-2c1e-bf12-53a7b46791e6": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "html_value_card", "type": "latest", - "title": "New widget", "sizeX": 8, "sizeY": 3, "config": { @@ -496,8 +498,27 @@ } ], "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { - "timewindowMs": 60000 + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694085343280, + "endTimeMs": 1694171743280 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 } }, "showTitle": false, @@ -545,14 +566,11 @@ "enableDataExport": false, "displayTimewindow": true }, - "id": "17543c57-af4a-2c1e-bf12-53a7b46791e6" + "id": "17543c57-af4a-2c1e-bf12-53a7b46791e6", + "typeFullFqn": "system.cards.html_value_card" }, "6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "html_value_card", "type": "latest", - "title": "New widget", "sizeX": 8, "sizeY": 3, "config": { @@ -580,8 +598,27 @@ } ], "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { - "timewindowMs": 60000 + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694085343280, + "endTimeMs": 1694171743280 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 } }, "showTitle": false, @@ -629,14 +666,11 @@ "enableDataExport": false, "displayTimewindow": true }, - "id": "6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6" + "id": "6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6", + "typeFullFqn": "system.cards.html_value_card" }, "e6674227-9cf3-a2f6-ecac-5ccfc38a3c81": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "html_value_card", "type": "latest", - "title": "New widget", "sizeX": 8, "sizeY": 3, "config": { @@ -664,8 +698,27 @@ } ], "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { - "timewindowMs": 60000 + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694085343280, + "endTimeMs": 1694171743280 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 } }, "showTitle": false, @@ -713,14 +766,11 @@ "enableDataExport": false, "displayTimewindow": true }, - "id": "e6674227-9cf3-a2f6-ecac-5ccfc38a3c81" + "id": "e6674227-9cf3-a2f6-ecac-5ccfc38a3c81", + "typeFullFqn": "system.cards.html_value_card" }, "77b10144-b904-edd5-8c7c-8fb75616c6d8": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "html_value_card", "type": "latest", - "title": "New widget", "sizeX": 8, "sizeY": 3, "config": { @@ -748,8 +798,27 @@ } ], "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { - "timewindowMs": 60000 + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694085343280, + "endTimeMs": 1694171743280 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 } }, "showTitle": false, @@ -797,23 +866,32 @@ "enableDataExport": false, "displayTimewindow": true }, - "id": "77b10144-b904-edd5-8c7c-8fb75616c6d8" + "id": "77b10144-b904-edd5-8c7c-8fb75616c6d8", + "typeFullFqn": "system.cards.html_value_card" }, "21be08bb-ec90-f760-ad6f-e7678f12c401": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "entities_table", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 7.5, "sizeY": 6.5, "config": { "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { + "realtimeType": 1, "interval": 1000, - "timewindowMs": 86400000 + "timewindowMs": 86400000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694085343280, + "endTimeMs": 1694171743280 + }, + "quickInterval": "CURRENT_DAY" }, "aggregation": { "type": "NONE", @@ -1094,23 +1172,32 @@ }, "row": 0, "col": 0, - "id": "21be08bb-ec90-f760-ad6f-e7678f12c401" + "id": "21be08bb-ec90-f760-ad6f-e7678f12c401", + "typeFullFqn": "system.cards.entities_table" }, "e8280043-d3dc-7acb-c2ff-a4522972ff91": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "entities_table", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 7.5, "sizeY": 6.5, "config": { "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 86400000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, "interval": 1000, - "timewindowMs": 86400000 + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694085343280, + "endTimeMs": 1694171743280 + }, + "quickInterval": "CURRENT_DAY" }, "aggregation": { "type": "NONE", @@ -1391,23 +1478,32 @@ }, "row": 0, "col": 0, - "id": "e8280043-d3dc-7acb-c2ff-a4522972ff91" + "id": "e8280043-d3dc-7acb-c2ff-a4522972ff91", + "typeFullFqn": "system.cards.entities_table" }, "3624013b-378c-f110-5eba-ae95c25a4dcc": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "entities_table", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 7.5, "sizeY": 6.5, "config": { "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 86400000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, "interval": 1000, - "timewindowMs": 86400000 + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694085343280, + "endTimeMs": 1694171743280 + }, + "quickInterval": "CURRENT_DAY" }, "aggregation": { "type": "NONE", @@ -1688,23 +1784,32 @@ }, "row": 0, "col": 0, - "id": "3624013b-378c-f110-5eba-ae95c25a4dcc" + "id": "3624013b-378c-f110-5eba-ae95c25a4dcc", + "typeFullFqn": "system.cards.entities_table" }, "d2d13e0d-4e71-889f-9343-ad2f0af9f176": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "entities_table", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 7.5, "sizeY": 6.5, "config": { "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { + "realtimeType": 1, "interval": 1000, - "timewindowMs": 86400000 + "timewindowMs": 86400000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694085343280, + "endTimeMs": 1694171743280 + }, + "quickInterval": "CURRENT_DAY" }, "aggregation": { "type": "NONE", @@ -1985,7 +2090,8 @@ }, "row": 0, "col": 0, - "id": "d2d13e0d-4e71-889f-9343-ad2f0af9f176" + "id": "d2d13e0d-4e71-889f-9343-ad2f0af9f176", + "typeFullFqn": "system.cards.entities_table" } }, "states": { @@ -2035,7 +2141,8 @@ "autoFillHeight": true, "backgroundImageUrl": null, "mobileAutoFillHeight": true, - "mobileRowHeight": 70 + "mobileRowHeight": 70, + "outerMargin": true } } } @@ -2062,7 +2169,8 @@ "autoFillHeight": true, "backgroundImageUrl": null, "mobileAutoFillHeight": false, - "mobileRowHeight": 70 + "mobileRowHeight": 70, + "outerMargin": true } } } @@ -2089,7 +2197,8 @@ "autoFillHeight": true, "backgroundImageUrl": null, "mobileAutoFillHeight": false, - "mobileRowHeight": 70 + "mobileRowHeight": 70, + "outerMargin": true } } } @@ -2116,7 +2225,8 @@ "autoFillHeight": true, "backgroundImageUrl": null, "mobileAutoFillHeight": false, - "mobileRowHeight": 70 + "mobileRowHeight": 70, + "outerMargin": true } } } @@ -2143,7 +2253,8 @@ "autoFillHeight": true, "backgroundImageUrl": null, "mobileAutoFillHeight": false, - "mobileRowHeight": 70 + "mobileRowHeight": 70, + "outerMargin": true } } } @@ -2170,7 +2281,8 @@ "autoFillHeight": true, "backgroundImageUrl": null, "mobileAutoFillHeight": false, - "mobileRowHeight": 70 + "mobileRowHeight": 70, + "outerMargin": true } } } @@ -2488,5 +2600,6 @@ "showUpdateDashboardImage": false } }, + "externalId": null, "name": "Software" } \ 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 c014d8875a..967c1cb253 100644 --- a/application/src/main/data/json/demo/dashboards/thermostats.json +++ b/application/src/main/data/json/demo/dashboards/thermostats.json @@ -1,20 +1,33 @@ { "title": "Thermostats", + "image": null, + "mobileHide": false, + "mobileOrder": null, "configuration": { "widgets": { "f33c746c-0dfc-c212-395b-b448c8a17209": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "entities_table", "type": "latest", - "title": "New widget", "sizeX": 11, "sizeY": 11, "config": { "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { + "realtimeType": 1, "interval": 1000, - "timewindowMs": 86400000 + "timewindowMs": 86400000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694085177686, + "endTimeMs": 1694171577686 + }, + "quickInterval": "CURRENT_DAY" }, "aggregation": { "type": "NONE", @@ -184,14 +197,11 @@ "rowClick": [] } }, - "id": "f33c746c-0dfc-c212-395b-b448c8a17209" + "id": "f33c746c-0dfc-c212-395b-b448c8a17209", + "typeFullFqn": "system.cards.entities_table" }, "7943196b-eedb-d422-f9c3-b32d379ad172": { - "isSystemType": true, - "bundleAlias": "alarm_widgets", - "typeAlias": "alarms_table", "type": "alarm", - "title": "New widget", "sizeX": 13, "sizeY": 5, "config": { @@ -230,7 +240,7 @@ "titleStyle": { "fontSize": "16px", "fontWeight": 400, - "padding": "5px 10px 5px 10px" + "padding": "5px 10px 5px 2px" }, "useDashboardTimewindow": false, "showLegend": false, @@ -290,7 +300,6 @@ } ] }, - "alarmSearchStatus": "ANY", "alarmsPollingInterval": 5, "showTitleIcon": false, "titleIcon": null, @@ -302,16 +311,22 @@ "actions": {}, "datasources": [], "alarmsMaxCountLoad": 0, - "alarmsFetchSize": 100 + "alarmsFetchSize": 100, + "alarmFilterConfig": { + "statusList": [], + "severityList": [], + "typeList": [], + "searchPropagatedAlarms": true + }, + "widgetCss": "", + "pageSize": 1024, + "noDataDisplayMessage": "" }, - "id": "7943196b-eedb-d422-f9c3-b32d379ad172" + "id": "7943196b-eedb-d422-f9c3-b32d379ad172", + "typeFullFqn": "system.alarm_widgets.alarms_table" }, "14a19183-f0b2-d6be-0f62-9863f0a51111": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "basic_timeseries", "type": "timeseries", - "title": "New widget", "sizeX": 18, "sizeY": 6, "config": { @@ -395,7 +410,16 @@ "axisPosition": "top", "showLabels": true }, - "smoothLines": true + "smoothLines": true, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "showMin": true, + "showMax": true, + "showAvg": true, + "showTotal": false + } }, "title": "Temperature", "dropShadow": true, @@ -413,25 +437,13 @@ "widgetStyle": {}, "useDashboardTimewindow": false, "displayTimewindow": true, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "bottom", - "showMin": true, - "showMax": true, - "showAvg": true, - "showTotal": false - }, "actions": {} }, - "id": "14a19183-f0b2-d6be-0f62-9863f0a51111" + "id": "14a19183-f0b2-d6be-0f62-9863f0a51111", + "typeFullFqn": "system.charts.basic_timeseries" }, "07f49fd5-a73b-d74c-c220-362c20af81f4": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "basic_timeseries", "type": "timeseries", - "title": "New widget", "sizeX": 18, "sizeY": 6, "config": { @@ -515,7 +527,16 @@ "axisPosition": "top", "showLabels": true }, - "smoothLines": true + "smoothLines": true, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "showMin": true, + "showMax": true, + "showAvg": true, + "showTotal": false + } }, "title": "Humidity", "dropShadow": true, @@ -533,25 +554,13 @@ "widgetStyle": {}, "useDashboardTimewindow": false, "displayTimewindow": true, - "showLegend": true, - "legendConfig": { - "direction": "column", - "position": "bottom", - "showMin": true, - "showMax": true, - "showAvg": true, - "showTotal": false - }, "actions": {} }, - "id": "07f49fd5-a73b-d74c-c220-362c20af81f4" + "id": "07f49fd5-a73b-d74c-c220-362c20af81f4", + "typeFullFqn": "system.charts.basic_timeseries" }, "c4631f94-2db3-523b-4d09-2a1a0a75d93f": { - "isSystemType": true, - "bundleAlias": "input_widgets", - "typeAlias": "update_multiple_attributes", "type": "latest", - "title": "New widget", "sizeX": 6, "sizeY": 6, "config": { @@ -626,8 +635,27 @@ } ], "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { - "timewindowMs": 60000 + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694085177686, + "endTimeMs": 1694171577686 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 } }, "showTitle": true, @@ -661,14 +689,11 @@ "titleTooltip": "", "displayTimewindow": true }, - "id": "c4631f94-2db3-523b-4d09-2a1a0a75d93f" + "id": "c4631f94-2db3-523b-4d09-2a1a0a75d93f", + "typeFullFqn": "system.input_widgets.update_multiple_attributes" }, "3da9a9a1-0b9a-2e1f-0dcb-0ff34a695abb": { - "isSystemType": true, - "bundleAlias": "maps_v2", - "typeAlias": "openstreetmap", "type": "latest", - "title": "New widget", "sizeX": 13, "sizeY": 6, "config": { @@ -725,8 +750,27 @@ } ], "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { - "timewindowMs": 60000 + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694085177686, + "endTimeMs": 1694171577686 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 } }, "showTitle": false, @@ -802,14 +846,11 @@ "titleTooltip": "", "displayTimewindow": true }, - "id": "3da9a9a1-0b9a-2e1f-0dcb-0ff34a695abb" + "id": "3da9a9a1-0b9a-2e1f-0dcb-0ff34a695abb", + "typeFullFqn": "system.maps_v2.openstreetmap" }, "00fb2742-ba1f-7e43-673f-d6c08b72ed06": { - "isSystemType": true, - "bundleAlias": "input_widgets", - "typeAlias": "markers_placement_openstreetmap", "type": "latest", - "title": "New widget", "sizeX": 24, "sizeY": 12, "config": { @@ -864,8 +905,27 @@ } ], "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { - "timewindowMs": 60000 + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694085177686, + "endTimeMs": 1694171577686 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 } }, "showTitle": false, @@ -937,14 +997,11 @@ "titleTooltip": "", "displayTimewindow": true }, - "id": "00fb2742-ba1f-7e43-673f-d6c08b72ed06" + "id": "00fb2742-ba1f-7e43-673f-d6c08b72ed06", + "typeFullFqn": "system.input_widgets.markers_placement_openstreetmap" }, "0a430429-9078-9ae6-2b67-e4a15a2bf8bf": { - "isSystemType": true, - "bundleAlias": "input_widgets", - "typeAlias": "markers_placement_openstreetmap", "type": "latest", - "title": "New widget", "sizeX": 6, "sizeY": 6, "config": { @@ -999,8 +1056,27 @@ } ], "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { - "timewindowMs": 60000 + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694085177686, + "endTimeMs": 1694171577686 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 } }, "showTitle": false, @@ -1073,7 +1149,8 @@ "titleTooltip": "", "displayTimewindow": true }, - "id": "0a430429-9078-9ae6-2b67-e4a15a2bf8bf" + "id": "0a430429-9078-9ae6-2b67-e4a15a2bf8bf", + "typeFullFqn": "system.input_widgets.markers_placement_openstreetmap" } }, "states": { @@ -1110,7 +1187,8 @@ "autoFillHeight": true, "mobileAutoFillHeight": false, "mobileRowHeight": 70, - "margin": 10 + "margin": 10, + "outerMargin": true } } } @@ -1136,7 +1214,8 @@ "autoFillHeight": true, "mobileAutoFillHeight": false, "mobileRowHeight": 70, - "margin": 10 + "margin": 10, + "outerMargin": true } } } @@ -1182,7 +1261,8 @@ "autoFillHeight": true, "mobileAutoFillHeight": false, "mobileRowHeight": 70, - "margin": 10 + "margin": 10, + "outerMargin": true } } } @@ -1195,8 +1275,10 @@ "filter": { "type": "deviceType", "resolveMultiple": true, - "deviceType": "thermostat", - "deviceNameFilter": "" + "deviceNameFilter": "", + "deviceTypes": [ + "thermostat" + ] } }, "12ae98c7-1ea2-52cf-64d5-763e9d993547": { @@ -1245,5 +1327,6 @@ }, "filters": {} }, + "externalId": null, "name": "Thermostats" } \ No newline at end of file diff --git a/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json b/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json index de71ced3a5..790c9d36c7 100644 --- a/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json +++ b/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json @@ -48,8 +48,8 @@ "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", "name": "Save Client Attributes", "debugMode": false, + "configurationVersion": 1, "configuration": { - "version": 1, "scope": "CLIENT_SCOPE", "notifyDevice": "false", "sendAttributesUpdatedNotification": "false", diff --git a/application/src/main/data/json/tenant/device_profile/rule_chain_template.json b/application/src/main/data/json/tenant/device_profile/rule_chain_template.json index 7d1deb7e41..d03fcd0b7b 100644 --- a/application/src/main/data/json/tenant/device_profile/rule_chain_template.json +++ b/application/src/main/data/json/tenant/device_profile/rule_chain_template.json @@ -32,8 +32,8 @@ "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", "name": "Save Client Attributes", "debugMode": false, + "configurationVersion": 1, "configuration": { - "version": 1, "scope": "CLIENT_SCOPE", "notifyDevice": "false", "sendAttributesUpdatedNotification": "false", diff --git a/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json b/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json index 57c3a86dfb..419d30ed4c 100644 --- a/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json +++ b/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json @@ -31,8 +31,8 @@ "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", "name": "Save Client Attributes", "debugMode": false, + "configurationVersion": 1, "configuration": { - "version": 1, "scope": "CLIENT_SCOPE", "notifyDevice": "false", "sendAttributesUpdatedNotification": "false", diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java index 509e0fdbef..a37e03de1b 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java @@ -33,7 +33,10 @@ import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; +import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration; +import org.thingsboard.server.common.data.device.data.DeviceData; import org.thingsboard.server.common.data.device.data.PowerMode; +import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; 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.DisabledDeviceProfileProvisionConfiguration; @@ -45,6 +48,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType; +import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; @@ -76,18 +80,18 @@ public class DeviceBulkImportService extends AbstractBulkImportService { private final Lock findOrCreateDeviceProfileLock = new ReentrantLock(); @Override - protected void setEntityFields(Device entity, Map fields) { - ObjectNode additionalInfo = getOrCreateAdditionalInfoObj(entity); + protected void setEntityFields(Device device, Map fields) { + ObjectNode additionalInfo = getOrCreateAdditionalInfoObj(device); fields.forEach((columnType, value) -> { switch (columnType) { case NAME: - entity.setName(value); + device.setName(value); break; case TYPE: - entity.setType(value); + device.setType(value); break; case LABEL: - entity.setLabel(value); + device.setLabel(value); break; case DESCRIPTION: additionalInfo.set("description", new TextNode(value)); @@ -96,16 +100,17 @@ public class DeviceBulkImportService extends AbstractBulkImportService { additionalInfo.set("gateway", BooleanNode.valueOf(Boolean.parseBoolean(value))); break; } - entity.setAdditionalInfo(additionalInfo); + device.setAdditionalInfo(additionalInfo); }); + setUpDeviceConfiguration(device, fields); } @Override @SneakyThrows - protected Device saveEntity(SecurityUser user, Device entity, Map fields) { + protected Device saveEntity(SecurityUser user, Device device, Map fields) { DeviceCredentials deviceCredentials; try { - deviceCredentials = createDeviceCredentials(entity.getTenantId(), entity.getId(), fields); + deviceCredentials = createDeviceCredentials(device.getTenantId(), device.getId(), fields); deviceCredentialsService.formatCredentials(deviceCredentials); } catch (Exception e) { throw new DeviceCredentialsValidationException("Invalid device credentials: " + e.getMessage()); @@ -113,15 +118,15 @@ public class DeviceBulkImportService extends AbstractBulkImportService { DeviceProfile deviceProfile; if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.LWM2M_CREDENTIALS) { - deviceProfile = setUpLwM2mDeviceProfile(entity.getTenantId(), entity); - } else if (StringUtils.isNotEmpty(entity.getType())) { - deviceProfile = deviceProfileService.findOrCreateDeviceProfile(entity.getTenantId(), entity.getType()); + deviceProfile = setUpLwM2mDeviceProfile(device.getTenantId(), device); + } else if (StringUtils.isNotEmpty(device.getType())) { + deviceProfile = deviceProfileService.findOrCreateDeviceProfile(device.getTenantId(), device.getType()); } else { - deviceProfile = deviceProfileService.findDefaultDeviceProfile(entity.getTenantId()); + deviceProfile = deviceProfileService.findDefaultDeviceProfile(device.getTenantId()); } - entity.setDeviceProfileId(deviceProfile.getId()); + device.setDeviceProfileId(deviceProfile.getId()); - return tbDeviceService.saveDeviceWithCredentials(entity, deviceCredentials, user); + return tbDeviceService.saveDeviceWithCredentials(device, deviceCredentials, user); } @Override @@ -136,6 +141,22 @@ public class DeviceBulkImportService extends AbstractBulkImportService { entity.setCustomerId(user.getCustomerId()); } + private void setUpDeviceConfiguration(Device device, Map fields) { + if (fields.containsKey(BulkImportColumnType.SNMP_HOST)) { + SnmpDeviceTransportConfiguration transportConfiguration = new SnmpDeviceTransportConfiguration(); + transportConfiguration.setHost(fields.get(BulkImportColumnType.SNMP_HOST)); + transportConfiguration.setPort(Optional.ofNullable(fields.get(BulkImportColumnType.SNMP_PORT)) + .map(Integer::parseInt).orElse(161)); + transportConfiguration.setProtocolVersion(Optional.ofNullable(fields.get(BulkImportColumnType.SNMP_VERSION)) + .map(version -> SnmpProtocolVersion.valueOf(version.toUpperCase())).orElse(SnmpProtocolVersion.V2C)); + transportConfiguration.setCommunity(fields.getOrDefault(BulkImportColumnType.SNMP_COMMUNITY_STRING, "public")); + + DeviceData deviceData = new DeviceData(); + deviceData.setTransportConfiguration(transportConfiguration); + device.setDeviceData(deviceData); + } + } + @SneakyThrows private DeviceCredentials createDeviceCredentials(TenantId tenantId, DeviceId deviceId, Map fields) { DeviceCredentials credentials = new DeviceCredentials(); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java index fd341365cd..cfb92d0bfd 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java @@ -614,13 +614,13 @@ public final class EdgeGrpcSession implements Closeable { case EDGE: return ctx.getEdgeProcessor().convertEdgeEventToDownlink(edgeEvent); case DEVICE: - return ctx.getDeviceProcessor().convertDeviceEventToDownlink(edgeEvent); + return ctx.getDeviceProcessor().convertDeviceEventToDownlink(edgeEvent, this.edge.getId(), this.edgeVersion); case DEVICE_PROFILE: - return ctx.getDeviceProfileProcessor().convertDeviceProfileEventToDownlink(edgeEvent); + return ctx.getDeviceProfileProcessor().convertDeviceProfileEventToDownlink(edgeEvent, this.edge.getId(), this.edgeVersion); case ASSET_PROFILE: - return ctx.getAssetProfileProcessor().convertAssetProfileEventToDownlink(edgeEvent); + return ctx.getAssetProfileProcessor().convertAssetProfileEventToDownlink(edgeEvent, this.edge.getId(), this.edgeVersion); case ASSET: - return ctx.getAssetProcessor().convertAssetEventToDownlink(edgeEvent); + return ctx.getAssetProcessor().convertAssetEventToDownlink(edgeEvent, this.edge.getId(), this.edgeVersion); case ENTITY_VIEW: return ctx.getEntityViewProcessor().convertEntityViewEventToDownlink(edgeEvent); case DASHBOARD: diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java index 54aa99a5fd..328a64efe2 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java @@ -79,6 +79,7 @@ import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetsBundleService; +import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.TbQueueCallback; @@ -596,4 +597,57 @@ public abstract class BaseEdgeProcessor { } }); } + + protected AssetProfile checkIfAssetProfileDefaultFieldsAssignedToEdge(TenantId tenantId, EdgeId edgeId, AssetProfile assetProfile, EdgeVersion edgeVersion) { + switch (edgeVersion) { + case V_3_3_3: + case V_3_3_0: + case V_3_4_0: + if (assetProfile.getDefaultDashboardId() != null + && isEntityNotAssignedToEdge(tenantId, assetProfile.getDefaultDashboardId(), edgeId)) { + assetProfile.setDefaultDashboardId(null); + } + if (assetProfile.getDefaultEdgeRuleChainId() != null + && isEntityNotAssignedToEdge(tenantId, assetProfile.getDefaultEdgeRuleChainId(), edgeId)) { + assetProfile.setDefaultEdgeRuleChainId(null); + } + break; + } + return assetProfile; + } + + protected DeviceProfile checkIfDeviceProfileDefaultFieldsAssignedToEdge(TenantId tenantId, EdgeId edgeId, DeviceProfile deviceProfile, EdgeVersion edgeVersion) { + switch (edgeVersion) { + case V_3_3_3: + case V_3_3_0: + case V_3_4_0: + if (deviceProfile.getDefaultDashboardId() != null + && isEntityNotAssignedToEdge(tenantId, deviceProfile.getDefaultDashboardId(), edgeId)) { + deviceProfile.setDefaultDashboardId(null); + } + if (deviceProfile.getDefaultEdgeRuleChainId() != null + && isEntityNotAssignedToEdge(tenantId, deviceProfile.getDefaultEdgeRuleChainId(), edgeId)) { + deviceProfile.setDefaultEdgeRuleChainId(null); + } + break; + } + return deviceProfile; + } + + private boolean isEntityNotAssignedToEdge(TenantId tenantId, EntityId entityId, EdgeId edgeId) { + PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); + PageData pageData; + do { + pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink); + if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { + if (pageData.getData().contains(edgeId)) { + return false; + } + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } + } while (pageData != null && pageData.hasNext()); + return true; + } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java index aa0679a632..6e3c56d3b3 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -37,6 +38,7 @@ import org.thingsboard.server.dao.asset.BaseAssetService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -106,7 +108,7 @@ public class AssetEdgeProcessor extends BaseAssetProcessor { } } - public DownlinkMsg convertAssetEventToDownlink(EdgeEvent edgeEvent) { + public DownlinkMsg convertAssetEventToDownlink(EdgeEvent edgeEvent, EdgeId edgeId, EdgeVersion edgeVersion) { AssetId assetId = new AssetId(edgeEvent.getEntityId()); DownlinkMsg downlinkMsg = null; switch (edgeEvent.getAction()) { @@ -125,6 +127,7 @@ public class AssetEdgeProcessor extends BaseAssetProcessor { .addAssetUpdateMsg(assetUpdateMsg); if (UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE.equals(msgType)) { AssetProfile assetProfile = assetProfileService.findAssetProfileById(edgeEvent.getTenantId(), asset.getAssetProfileId()); + assetProfile = checkIfAssetProfileDefaultFieldsAssignedToEdge(edgeEvent.getTenantId(), edgeId, assetProfile, edgeVersion); builder.addAssetProfileUpdateMsg(assetProfileMsgConstructor.constructAssetProfileUpdatedMsg(msgType, assetProfile)); } downlinkMsg = builder.build(); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProfileEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProfileEdgeProcessor.java index 3fa69ed078..aceab6692c 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProfileEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProfileEdgeProcessor.java @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.AssetProfileId; import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; @@ -36,6 +37,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -94,7 +96,7 @@ public class AssetProfileEdgeProcessor extends BaseAssetProfileProcessor { } } - public DownlinkMsg convertAssetProfileEventToDownlink(EdgeEvent edgeEvent) { + public DownlinkMsg convertAssetProfileEventToDownlink(EdgeEvent edgeEvent, EdgeId edgeId, EdgeVersion edgeVersion) { AssetProfileId assetProfileId = new AssetProfileId(edgeEvent.getEntityId()); DownlinkMsg downlinkMsg = null; switch (edgeEvent.getAction()) { @@ -103,6 +105,7 @@ public class AssetProfileEdgeProcessor extends BaseAssetProfileProcessor { AssetProfile assetProfile = assetProfileService.findAssetProfileById(edgeEvent.getTenantId(), assetProfileId); if (assetProfile != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); + assetProfile = checkIfAssetProfileDefaultFieldsAssignedToEdge(edgeEvent.getTenantId(), edgeId, assetProfile, edgeVersion); AssetProfileUpdateMsg assetProfileUpdateMsg = assetProfileMsgConstructor.constructAssetProfileUpdatedMsg(msgType, assetProfile); downlinkMsg = DownlinkMsg.newBuilder() diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java index 15edd4215d..7b1da9a297 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java @@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.rpc.RpcError; @@ -49,6 +50,7 @@ import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg; import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueMsgMetadata; @@ -202,7 +204,7 @@ public class DeviceEdgeProcessor extends BaseDeviceProcessor { return Futures.immediateFuture(null); } - public DownlinkMsg convertDeviceEventToDownlink(EdgeEvent edgeEvent) { + public DownlinkMsg convertDeviceEventToDownlink(EdgeEvent edgeEvent, EdgeId edgeId, EdgeVersion edgeVersion) { DeviceId deviceId = new DeviceId(edgeEvent.getEntityId()); DownlinkMsg downlinkMsg = null; switch (edgeEvent.getAction()) { @@ -221,6 +223,7 @@ public class DeviceEdgeProcessor extends BaseDeviceProcessor { .addDeviceUpdateMsg(deviceUpdateMsg); if (UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE.equals(msgType)) { DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(edgeEvent.getTenantId(), device.getDeviceProfileId()); + deviceProfile = checkIfDeviceProfileDefaultFieldsAssignedToEdge(edgeEvent.getTenantId(), edgeId, deviceProfile, edgeVersion); builder.addDeviceProfileUpdateMsg(deviceProfileMsgConstructor.constructDeviceProfileUpdatedMsg(msgType, deviceProfile)); } downlinkMsg = builder.build(); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProfileEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProfileEdgeProcessor.java index 8f1e711775..d482f42cf6 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProfileEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProfileEdgeProcessor.java @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; @@ -36,6 +37,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -94,7 +96,7 @@ public class DeviceProfileEdgeProcessor extends BaseDeviceProfileProcessor { } } - public DownlinkMsg convertDeviceProfileEventToDownlink(EdgeEvent edgeEvent) { + public DownlinkMsg convertDeviceProfileEventToDownlink(EdgeEvent edgeEvent, EdgeId edgeId, EdgeVersion edgeVersion) { DeviceProfileId deviceProfileId = new DeviceProfileId(edgeEvent.getEntityId()); DownlinkMsg downlinkMsg = null; switch (edgeEvent.getAction()) { @@ -103,6 +105,7 @@ public class DeviceProfileEdgeProcessor extends BaseDeviceProfileProcessor { DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(edgeEvent.getTenantId(), deviceProfileId); if (deviceProfile != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); + deviceProfile = checkIfDeviceProfileDefaultFieldsAssignedToEdge(edgeEvent.getTenantId(), edgeId, deviceProfile, edgeVersion); DeviceProfileUpdateMsg deviceProfileUpdateMsg = deviceProfileMsgConstructor.constructDeviceProfileUpdatedMsg(msgType, deviceProfile); downlinkMsg = DownlinkMsg.newBuilder() 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 40596e03e9..7f0b70cb27 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 @@ -28,7 +28,6 @@ import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; @@ -104,15 +103,15 @@ public class InstallScripts { @Autowired private ResourceService resourceService; - private Path getTenantRuleChainsDir() { + Path getTenantRuleChainsDir() { return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, RULE_CHAINS_DIR); } - private Path getDeviceProfileDefaultRuleChainTemplateFilePath() { + Path getDeviceProfileDefaultRuleChainTemplateFilePath() { return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, DEVICE_PROFILE_DIR, "rule_chain_template.json"); } - private Path getEdgeRuleChainsDir() { + Path getEdgeRuleChainsDir() { return Paths.get(getDataDir(), JSON_DIR, EDGE_DIR, RULE_CHAINS_DIR); } @@ -148,8 +147,8 @@ public class InstallScripts { } private void loadRuleChainsFromPath(TenantId tenantId, Path ruleChainsPath) throws IOException { - try (DirectoryStream dirStream = Files.newDirectoryStream(ruleChainsPath, path -> path.toString().endsWith(InstallScripts.JSON_EXT))) { - dirStream.forEach( + findRuleChainsFromPath(ruleChainsPath) + .forEach( path -> { try { createRuleChainFromFile(tenantId, path, null); @@ -157,9 +156,15 @@ public class InstallScripts { log.error("Unable to load rule chain from json: [{}]", path.toString()); throw new RuntimeException("Unable to load rule chain from json", e); } - } - ); + }); + } + + List findRuleChainsFromPath(Path ruleChainsPath) throws IOException { + List paths = new ArrayList<>(); + try (DirectoryStream dirStream = Files.newDirectoryStream(ruleChainsPath, path -> path.toString().endsWith(InstallScripts.JSON_EXT))) { + dirStream.forEach(paths::add); } + return paths; } public RuleChain createDefaultRuleChain(TenantId tenantId, String ruleChainName) throws IOException { 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 fef22acf40..fd5a252cc5 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 @@ -24,10 +24,14 @@ import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; +import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.common.data.alarm.AlarmInfo; +import org.thingsboard.server.common.data.event.ErrorEvent; +import org.thingsboard.server.common.data.event.Event; +import org.thingsboard.server.common.data.event.LifecycleEvent; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.TenantId; @@ -45,7 +49,9 @@ import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.EdgeNotificationMsgProto; +import org.thingsboard.server.gen.transport.TransportProtos.ErrorEventProto; import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto; +import org.thingsboard.server.gen.transport.TransportProtos.LifecycleEventProto; import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionMgrMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmDeleteProto; @@ -274,6 +280,10 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService callback.onSuccess(), + callback::onFailure, + actorContext.getDbCallbackExecutor()); + } + private void throwNotHandled(Object msg, TbCallback callback) { log.warn("Message not handled: {}", msg); callback.onFailure(new RuntimeException("Message not handled!")); } + private TenantId toTenantId(long tenantIdMSB, long tenantIdLSB) { + return TenantId.fromUUID(new UUID(tenantIdMSB, tenantIdLSB)); + } + @Override protected void stopMainConsumers() { if (mainConsumer != null) { diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java index 45805c5939..c03dd8dcb7 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java @@ -22,6 +22,7 @@ import com.google.gson.JsonPrimitive; import lombok.Data; import lombok.SneakyThrows; import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.commons.lang3.tuple.Pair; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; @@ -57,7 +58,7 @@ import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import org.thingsboard.server.utils.CsvUtils; -import org.thingsboard.server.utils.TypeCastUtil; +import org.thingsboard.server.common.data.util.TypeCastUtil; import javax.annotation.Nullable; import javax.annotation.PostConstruct; @@ -269,7 +270,7 @@ public abstract class AbstractBulkImportService castResult = TypeCastUtil.castValue(entry.getValue()); + Pair castResult = TypeCastUtil.castValue(entry.getValue()); entityData.getKvs().put(entry.getKey(), new ParsedValue(castResult.getValue(), castResult.getKey())); } }); diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 6e745f063f..306e9a798c 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -991,11 +991,15 @@ transport: # value: "${LWM2M_PROTOCOL_STAGE_THREAD_COUNT:4}" snmp: enabled: "${SNMP_ENABLED:true}" + bind_port: "${SNMP_BIND_PORT:1620}" response_processing: # parallelism level for executor (workStealingPool) that is responsible for handling responses from SNMP devices parallelism_level: "${SNMP_RESPONSE_PROCESSING_PARALLELISM_LEVEL:20}" # to configure SNMP to work over UDP or TCP underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}" + max_request_oids: "${SNMP_MAX_REQUEST_OIDS:100}" + response: + ignore_type_cast_errors: "${SNMP_RESPONSE_IGNORE_TYPE_CAST_ERRORS:false}" stats: enabled: "${TB_TRANSPORT_STATS_ENABLED:true}" print-interval-ms: "${TB_TRANSPORT_STATS_PRINT_INTERVAL_MS:60000}" diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java index 525e18240d..237f900d47 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java @@ -66,6 +66,8 @@ import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.PEM_CERT_FI @TestPropertySource(properties = { "device.connectivity.https.enabled=true", + "device.connectivity.http.port=8080", + "device.connectivity.https.port=444", "device.connectivity.mqtts.enabled=true", "device.connectivity.mqtts.pem_cert_file=/tmp/" + PEM_CERT_FILE_NAME, "device.connectivity.coaps.enabled=true", @@ -191,7 +193,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { assertThat(httpCommands.get(HTTP).asText()).isEqualTo(String.format("curl -v -X POST http://localhost:8080/api/v1/%s/telemetry " + "--header Content-Type:application/json --data \"{temperature:25}\"", credentials.getCredentialsId())); - assertThat(httpCommands.get(HTTPS).asText()).isEqualTo(String.format("curl -v -X POST https://localhost:443/api/v1/%s/telemetry " + + assertThat(httpCommands.get(HTTPS).asText()).isEqualTo(String.format("curl -v -X POST https://localhost:444/api/v1/%s/telemetry " + "--header Content-Type:application/json --data \"{temperature:25}\"", credentials.getCredentialsId())); diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerWithDefaultPortTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerWithDefaultPortTest.java new file mode 100644 index 0000000000..8ca0d07540 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerWithDefaultPortTest.java @@ -0,0 +1,144 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.controller; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +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; +import org.junit.Test; +import org.mockito.AdditionalAnswers; +import org.mockito.Mockito; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.common.util.ThingsBoardExecutors; +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.User; +import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; +import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; +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.id.DeviceProfileId; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.dao.device.DeviceDao; +import org.thingsboard.server.dao.service.DaoSqlTest; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAP; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAPS; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.DOCKER; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTP; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTPS; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTT; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTTS; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.PEM_CERT_FILE_NAME; + +@TestPropertySource(properties = { + "device.connectivity.https.enabled=true", + "device.connectivity.http.port=80", + "device.connectivity.mqtt.enabled=false", + "device.connectivity.mqtts.enabled=false", + "device.connectivity.coap.enabled=false", + "device.connectivity.coaps.enabled=false", +}) +@ContextConfiguration(classes = {DeviceConnectivityControllerWithDefaultPortTest.Config.class}) +@DaoSqlTest +public class DeviceConnectivityControllerWithDefaultPortTest extends AbstractControllerTest { + + ListeningExecutorService executor; + + private Tenant savedTenant; + + static class Config { + @Bean + @Primary + public DeviceDao deviceDao(DeviceDao deviceDao) { + return Mockito.mock(DeviceDao.class, AdditionalAnswers.delegatesTo(deviceDao)); + } + } + + @Before + public void beforeTest() throws Exception { + executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass())); + + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + User tenantAdmin = new User(); + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin.setTenantId(savedTenant.getId()); + tenantAdmin.setEmail("tenant2@thingsboard.org"); + tenantAdmin.setFirstName("Joe"); + tenantAdmin.setLastName("Downs"); + + createUserAndLogin(tenantAdmin, "testPassword1"); + } + + @After + public void afterTest() throws Exception { + executor.shutdownNow(); + + loginSysAdmin(); + + doDelete("/api/tenant/" + savedTenant.getId().getId()) + .andExpect(status().isOk()); + } + + @Test + public void testFetchPublishTelemetryCommandsForDefaultDevice() throws Exception { + Device device = new Device(); + device.setName("My device"); + device.setType("default"); + Device savedDevice = doPost("/api/device", device, Device.class); + JsonNode commands = + doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() { + }); + + DeviceCredentials credentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + + assertThat(commands).hasSize(1); + JsonNode httpCommands = commands.get(HTTP); + assertThat(httpCommands.get(HTTP).asText()).isEqualTo(String.format("curl -v -X POST http://localhost/api/v1/%s/telemetry " + + "--header Content-Type:application/json --data \"{temperature:25}\"", + credentials.getCredentialsId())); + assertThat(httpCommands.get(HTTPS).asText()).isEqualTo(String.format("curl -v -X POST https://localhost/api/v1/%s/telemetry " + + "--header Content-Type:application/json --data \"{temperature:25}\"", + credentials.getCredentialsId())); + } +} diff --git a/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java new file mode 100644 index 0000000000..1c4b481302 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java @@ -0,0 +1,306 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.processor; + +import org.junit.jupiter.params.provider.Arguments; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.context.annotation.Lazy; +import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.Dashboard; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.asset.AssetProfile; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.alarm.AlarmService; +import org.thingsboard.server.dao.asset.AssetProfileService; +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.dashboard.DashboardService; +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.edge.EdgeEventService; +import org.thingsboard.server.dao.edge.EdgeService; +import org.thingsboard.server.dao.edge.EdgeSynchronizationManager; +import org.thingsboard.server.dao.entityview.EntityViewService; +import org.thingsboard.server.dao.ota.OtaPackageService; +import org.thingsboard.server.dao.queue.QueueService; +import org.thingsboard.server.dao.relation.RelationService; +import org.thingsboard.server.dao.rule.RuleChainService; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.tenant.TenantProfileService; +import org.thingsboard.server.dao.tenant.TenantService; +import org.thingsboard.server.dao.user.UserService; +import org.thingsboard.server.dao.widget.WidgetTypeService; +import org.thingsboard.server.dao.widget.WidgetsBundleService; +import org.thingsboard.server.gen.edge.v1.EdgeVersion; +import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.provider.TbQueueProducerProvider; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; +import org.thingsboard.server.service.edge.rpc.constructor.AdminSettingsMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.AlarmMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.AssetMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.AssetProfileMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.CustomerMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.DashboardMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.DeviceMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.DeviceProfileMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.EdgeMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.EntityDataMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.EntityViewMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.OtaPackageMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.QueueMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.RelationMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.RuleChainMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.TenantMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.TenantProfileMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.UserMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.WidgetTypeMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.WidgetsBundleMsgConstructor; +import org.thingsboard.server.service.entitiy.TbNotificationEntityService; +import org.thingsboard.server.service.executors.DbCallbackExecutorService; +import org.thingsboard.server.service.profile.TbAssetProfileCache; +import org.thingsboard.server.service.profile.TbDeviceProfileCache; +import org.thingsboard.server.service.state.DeviceStateService; +import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; + +import java.util.UUID; +import java.util.stream.Stream; + +public abstract class BaseEdgeProcessorTest { + + @MockBean + protected TelemetrySubscriptionService tsSubService; + + @MockBean + protected TbNotificationEntityService notificationEntityService; + + @MockBean + protected RuleChainService ruleChainService; + + @MockBean + protected AlarmService alarmService; + + @MockBean + protected DeviceService deviceService; + + @MockBean + protected TbDeviceProfileCache deviceProfileCache; + + @MockBean + protected TbAssetProfileCache assetProfileCache; + + @MockBean + protected DashboardService dashboardService; + + @MockBean + protected AssetService assetService; + + @MockBean + protected EntityViewService entityViewService; + + @MockBean + protected TenantService tenantService; + + @MockBean + protected TenantProfileService tenantProfileService; + + @MockBean + protected EdgeService edgeService; + + @MockBean + protected CustomerService customerService; + + @MockBean + protected UserService userService; + + @MockBean + protected DeviceProfileService deviceProfileService; + + @MockBean + protected AssetProfileService assetProfileService; + + @MockBean + protected RelationService relationService; + + @MockBean + protected DeviceCredentialsService deviceCredentialsService; + + @MockBean + protected AttributesService attributesService; + + @MockBean + protected TbClusterService tbClusterService; + + @MockBean + protected DeviceStateService deviceStateService; + + @MockBean + protected EdgeEventService edgeEventService; + + @MockBean + protected WidgetsBundleService widgetsBundleService; + + @MockBean + protected WidgetTypeService widgetTypeService; + + @MockBean + protected OtaPackageService otaPackageService; + + @MockBean + protected QueueService queueService; + + @MockBean + protected PartitionService partitionService; + + @MockBean + @Lazy + protected TbQueueProducerProvider producerProvider; + + @MockBean + protected DataValidator deviceValidator; + + @MockBean + protected DataValidator deviceProfileValidator; + + @MockBean + protected DataValidator assetValidator; + + @MockBean + protected DataValidator assetProfileValidator; + + @MockBean + protected DataValidator dashboardValidator; + + @MockBean + protected DataValidator entityViewValidator; + + @MockBean + protected EdgeMsgConstructor edgeMsgConstructor; + + @MockBean + protected EntityDataMsgConstructor entityDataMsgConstructor; + + @MockBean + protected RuleChainMsgConstructor ruleChainMsgConstructor; + + @MockBean + protected AlarmMsgConstructor alarmMsgConstructor; + + @SpyBean + protected DeviceMsgConstructor deviceMsgConstructor; + + @SpyBean + protected AssetMsgConstructor assetMsgConstructor; + + @MockBean + protected EntityViewMsgConstructor entityViewMsgConstructor; + + @MockBean + protected DashboardMsgConstructor dashboardMsgConstructor; + + @MockBean + protected RelationMsgConstructor relationMsgConstructor; + + @MockBean + protected UserMsgConstructor userMsgConstructor; + + @MockBean + protected CustomerMsgConstructor customerMsgConstructor; + + @SpyBean + protected DeviceProfileMsgConstructor deviceProfileMsgConstructor; + + @SpyBean + protected AssetProfileMsgConstructor assetProfileMsgConstructor; + + @MockBean + protected TenantMsgConstructor tenantMsgConstructor; + + @MockBean + protected TenantProfileMsgConstructor tenantProfileMsgConstructor; + + @MockBean + protected WidgetsBundleMsgConstructor widgetsBundleMsgConstructor; + + @MockBean + protected WidgetTypeMsgConstructor widgetTypeMsgConstructor; + + @MockBean + protected AdminSettingsMsgConstructor adminSettingsMsgConstructor; + + @MockBean + protected OtaPackageMsgConstructor otaPackageMsgConstructor; + + @MockBean + protected QueueMsgConstructor queueMsgConstructor; + + @MockBean + protected EdgeSynchronizationManager edgeSynchronizationManager; + + @MockBean + protected DbCallbackExecutorService dbCallbackExecutorService; + + @MockBean + protected DataDecodingEncodingService dataDecodingEncodingService; + + protected EdgeId edgeId; + protected TenantId tenantId; + protected EdgeEvent edgeEvent; + + protected DashboardId getDashboardId(long expectedDashboardIdMSB, long expectedDashboardIdLSB) { + DashboardId dashboardId; + if (expectedDashboardIdMSB != 0 && expectedDashboardIdLSB != 0) { + dashboardId = new DashboardId(new UUID(expectedDashboardIdMSB, expectedDashboardIdLSB)); + } else { + dashboardId = new DashboardId(UUID.randomUUID()); + } + return dashboardId; + } + + protected RuleChainId getRuleChainId(long expectedRuleChainIdMSB, long expectedRuleChainIdLSB) { + RuleChainId ruleChainId; + if (expectedRuleChainIdMSB != 0 && expectedRuleChainIdLSB != 0) { + ruleChainId = new RuleChainId(new UUID(expectedRuleChainIdMSB, expectedRuleChainIdLSB)); + } else { + ruleChainId = new RuleChainId(UUID.randomUUID()); + } + return ruleChainId; + } + + protected static Stream provideParameters() { + UUID dashoboardUUID = UUID.randomUUID(); + UUID ruleChaindUUID = UUID.randomUUID(); + return Stream.of( + Arguments.of(EdgeVersion.V_3_3_0, 0, 0, 0, 0), + Arguments.of(EdgeVersion.V_3_3_3, 0, 0, 0, 0), + Arguments.of(EdgeVersion.V_3_4_0, 0, 0, 0, 0), + Arguments.of(EdgeVersion.V_3_6_0, + dashoboardUUID.getMostSignificantBits(), + dashoboardUUID.getLeastSignificantBits(), + ruleChaindUUID.getMostSignificantBits(), + ruleChaindUUID.getLeastSignificantBits()) + ); + } +} diff --git a/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/asset/AbstractAssetProcessorTest.java b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/asset/AbstractAssetProcessorTest.java new file mode 100644 index 0000000000..6216c71440 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/asset/AbstractAssetProcessorTest.java @@ -0,0 +1,92 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.processor.asset; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.asset.AssetProfile; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.id.AssetId; +import org.thingsboard.server.common.data.id.AssetProfileId; +import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg; +import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessorTest; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.BDDMockito.willReturn; + +public abstract class AbstractAssetProcessorTest extends BaseEdgeProcessorTest { + + + protected AssetId assetId; + protected AssetProfileId assetProfileId; + protected AssetProfile assetProfile; + + @BeforeEach + public void setUp() { + edgeId = new EdgeId(UUID.randomUUID()); + tenantId = new TenantId(UUID.randomUUID()); + assetId = new AssetId(UUID.randomUUID()); + assetProfileId = new AssetProfileId(UUID.randomUUID()); + + assetProfile = new AssetProfile(); + assetProfile.setId(assetProfileId); + assetProfile.setName("AssetProfile"); + assetProfile.setDefault(true); + + Asset asset = new Asset(); + asset.setAssetProfileId(assetProfileId); + asset.setId(assetId); + asset.setName("Asset"); + asset.setType(assetProfile.getName()); + + edgeEvent = new EdgeEvent(); + edgeEvent.setTenantId(tenantId); + edgeEvent.setAction(EdgeEventActionType.ADDED); + + + willReturn(asset).given(assetService).findAssetById(tenantId, assetId); + willReturn(assetProfile).given(assetProfileService).findAssetProfileById(tenantId, assetProfileId); + } + + protected void updateAssetProfileDefaultFields(long expectedDashboardIdMSB, long expectedDashboardIdLSB, + long expectedRuleChainIdMSB, long expectedRuleChainIdLSB) { + DashboardId dashboardId = getDashboardId(expectedDashboardIdMSB, expectedDashboardIdLSB); + RuleChainId ruleChainId = getRuleChainId(expectedRuleChainIdMSB, expectedRuleChainIdLSB); + + assetProfile.setDefaultDashboardId(dashboardId); + assetProfile.setDefaultEdgeRuleChainId(ruleChainId); + + } + + protected void verify(DownlinkMsg downlinkMsg, long expectedDashboardIdMSB, long expectedDashboardIdLSB, + long expectedRuleChainIdMSB, long expectedRuleChainIdLSB) { + AssetProfileUpdateMsg assetProfileUpdateMsg = downlinkMsg.getAssetProfileUpdateMsgList().get(0); + assertNotNull(assetProfileUpdateMsg); + Assertions.assertThat(assetProfileUpdateMsg.getDefaultDashboardIdMSB()).isEqualTo(expectedDashboardIdMSB); + Assertions.assertThat(assetProfileUpdateMsg.getDefaultDashboardIdLSB()).isEqualTo(expectedDashboardIdLSB); + Assertions.assertThat(assetProfileUpdateMsg.getDefaultRuleChainIdMSB()).isEqualTo(expectedRuleChainIdMSB); + Assertions.assertThat(assetProfileUpdateMsg.getDefaultRuleChainIdLSB()).isEqualTo(expectedRuleChainIdLSB); + } +} diff --git a/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessorTest.java b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessorTest.java new file mode 100644 index 0000000000..d8c8041694 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessorTest.java @@ -0,0 +1,44 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.processor.asset; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import org.thingsboard.server.gen.edge.v1.EdgeVersion; + +@SpringBootTest(classes = {AssetEdgeProcessor.class}) +class AssetEdgeProcessorTest extends AbstractAssetProcessorTest { + + @SpyBean + AssetEdgeProcessor assetEdgeProcessor; + + @ParameterizedTest + @MethodSource("provideParameters") + public void testAssetProfileDefaultFields_notSendToEdgeOlder3_6_0IfNotAssigned(EdgeVersion edgeVersion, long expectedDashboardIdMSB, long expectedDashboardIdLSB, + long expectedRuleChainIdMSB, long expectedRuleChainIdLSB) { + updateAssetProfileDefaultFields(expectedDashboardIdMSB, expectedDashboardIdLSB, expectedRuleChainIdMSB, expectedRuleChainIdLSB); + + edgeEvent.setEntityId(assetId.getId()); + + DownlinkMsg downlinkMsg = assetEdgeProcessor.convertAssetEventToDownlink(edgeEvent, edgeId, edgeVersion); + + verify(downlinkMsg, expectedDashboardIdMSB, expectedDashboardIdLSB, expectedRuleChainIdMSB, expectedRuleChainIdLSB); + } + +} \ No newline at end of file diff --git a/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProfileEdgeProcessorTest.java b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProfileEdgeProcessorTest.java new file mode 100644 index 0000000000..e0a6155664 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProfileEdgeProcessorTest.java @@ -0,0 +1,43 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.processor.asset; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import org.thingsboard.server.gen.edge.v1.EdgeVersion; + +@SpringBootTest(classes = {AssetProfileEdgeProcessor.class}) +class AssetProfileEdgeProcessorTest extends AbstractAssetProcessorTest{ + @SpyBean + AssetProfileEdgeProcessor assetProfileEdgeProcessor; + + @ParameterizedTest + @MethodSource("provideParameters") + public void testAssetProfileDefaultFields_notSendToEdgeOlder3_6_0IfNotAssigned(EdgeVersion edgeVersion, long expectedDashboardIdMSB, long expectedDashboardIdLSB, + long expectedRuleChainIdMSB, long expectedRuleChainIdLSB) { + + updateAssetProfileDefaultFields(expectedDashboardIdMSB, expectedDashboardIdLSB, expectedRuleChainIdMSB, expectedRuleChainIdLSB); + + edgeEvent.setEntityId(assetProfileId.getId()); + + DownlinkMsg downlinkMsg = assetProfileEdgeProcessor.convertAssetProfileEventToDownlink(edgeEvent, edgeId, edgeVersion); + + verify(downlinkMsg, expectedDashboardIdMSB, expectedDashboardIdLSB, expectedRuleChainIdMSB, expectedRuleChainIdLSB); + } +} \ No newline at end of file diff --git a/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/device/AbstractDeviceProcessorTest.java b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/device/AbstractDeviceProcessorTest.java new file mode 100644 index 0000000000..6f2249b173 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/device/AbstractDeviceProcessorTest.java @@ -0,0 +1,100 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.processor.device; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +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.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; +import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessorTest; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.BDDMockito.willReturn; + +public abstract class AbstractDeviceProcessorTest extends BaseEdgeProcessorTest { + + protected DeviceId deviceId; + protected DeviceProfileId deviceProfileId; + protected DeviceProfile deviceProfile; + + @BeforeEach + public void setUp() { + edgeId = new EdgeId(UUID.randomUUID()); + tenantId = new TenantId(UUID.randomUUID()); + deviceId = new DeviceId(UUID.randomUUID()); + deviceProfileId = new DeviceProfileId(UUID.randomUUID()); + + deviceProfile = new DeviceProfile(); + deviceProfile.setId(deviceProfileId); + deviceProfile.setName("DeviceProfile"); + deviceProfile.setDefault(true); + deviceProfile.setType(DeviceProfileType.DEFAULT); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + deviceProfile.setProfileData(deviceProfileData); + deviceProfile.setTransportType(DeviceTransportType.DEFAULT); + + Device device = new Device(); + device.setDeviceProfileId(deviceProfileId); + device.setId(deviceId); + device.setName("Device"); + device.setType(deviceProfile.getName()); + + edgeEvent = new EdgeEvent(); + edgeEvent.setTenantId(tenantId); + edgeEvent.setAction(EdgeEventActionType.ADDED); + + + willReturn(device).given(deviceService).findDeviceById(tenantId, deviceId); + willReturn(deviceProfile).given(deviceProfileService).findDeviceProfileById(tenantId, deviceProfileId); + willReturn(new byte[]{0x00}).given(dataDecodingEncodingService).encode(deviceProfileData); + + } + + protected void updateDeviceProfileDefaultFields(long expectedDashboardIdMSB, long expectedDashboardIdLSB, + long expectedRuleChainIdMSB, long expectedRuleChainIdLSB) { + DashboardId dashboardId = getDashboardId(expectedDashboardIdMSB, expectedDashboardIdLSB); + RuleChainId ruleChainId = getRuleChainId(expectedRuleChainIdMSB, expectedRuleChainIdLSB); + + deviceProfile.setDefaultDashboardId(dashboardId); + deviceProfile.setDefaultEdgeRuleChainId(ruleChainId); + + } + + protected void verify(DownlinkMsg downlinkMsg, long expectedDashboardIdMSB, long expectedDashboardIdLSB, + long expectedRuleChainIdMSB, long expectedRuleChainIdLSB) { + DeviceProfileUpdateMsg deviceProfileUpdateMsg = downlinkMsg.getDeviceProfileUpdateMsgList().get(0); + assertNotNull(deviceProfileUpdateMsg); + Assertions.assertThat(deviceProfileUpdateMsg.getDefaultDashboardIdMSB()).isEqualTo(expectedDashboardIdMSB); + Assertions.assertThat(deviceProfileUpdateMsg.getDefaultDashboardIdLSB()).isEqualTo(expectedDashboardIdLSB); + Assertions.assertThat(deviceProfileUpdateMsg.getDefaultRuleChainIdMSB()).isEqualTo(expectedRuleChainIdMSB); + Assertions.assertThat(deviceProfileUpdateMsg.getDefaultRuleChainIdLSB()).isEqualTo(expectedRuleChainIdLSB); + } +} diff --git a/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessorTest.java b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessorTest.java new file mode 100644 index 0000000000..f64bd898d1 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessorTest.java @@ -0,0 +1,43 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.processor.device; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import org.thingsboard.server.gen.edge.v1.EdgeVersion; + +@SpringBootTest(classes = {DeviceEdgeProcessor.class}) +class DeviceEdgeProcessorTest extends AbstractDeviceProcessorTest { + + @SpyBean + DeviceEdgeProcessor deviceEdgeProcessor; + + @ParameterizedTest + @MethodSource("provideParameters") + public void testDeviceProfileDefaultFields_notSendToEdgeOlder3_6_0IfNotAssigned(EdgeVersion edgeVersion, long expectedDashboardIdMSB, long expectedDashboardIdLSB, + long expectedRuleChainIdMSB, long expectedRuleChainIdLSB) { + updateDeviceProfileDefaultFields(expectedDashboardIdMSB, expectedDashboardIdLSB, expectedRuleChainIdMSB, expectedRuleChainIdLSB); + edgeEvent.setEntityId(deviceId.getId()); + + DownlinkMsg downlinkMsg = deviceEdgeProcessor.convertDeviceEventToDownlink(edgeEvent, edgeId, edgeVersion); + + verify(downlinkMsg, expectedDashboardIdMSB, expectedDashboardIdLSB, expectedRuleChainIdMSB, expectedRuleChainIdLSB); + } + +} \ No newline at end of file diff --git a/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProfileEdgeProcessorTest.java b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProfileEdgeProcessorTest.java new file mode 100644 index 0000000000..35d44852e9 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProfileEdgeProcessorTest.java @@ -0,0 +1,45 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.processor.device; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import org.thingsboard.server.gen.edge.v1.EdgeVersion; + + +@SpringBootTest(classes = {DeviceProfileEdgeProcessor.class}) +class DeviceProfileEdgeProcessorTest extends AbstractDeviceProcessorTest { + + @SpyBean + DeviceProfileEdgeProcessor deviceProfileEdgeProcessor; + + @ParameterizedTest + @MethodSource("provideParameters") + public void testDeviceProfileDefaultFields_notSendToEdgeOlder3_6_0IfNotAssigned(EdgeVersion edgeVersion, long expectedDashboardIdMSB, long expectedDashboardIdLSB, + long expectedRuleChainIdMSB, long expectedRuleChainIdLSB) { + updateDeviceProfileDefaultFields(expectedDashboardIdMSB, expectedDashboardIdLSB, expectedRuleChainIdMSB, expectedRuleChainIdLSB); + + edgeEvent.setEntityId(deviceProfileId.getId()); + + DownlinkMsg downlinkMsg = deviceProfileEdgeProcessor.convertDeviceProfileEventToDownlink(edgeEvent, edgeId, edgeVersion); + + verify(downlinkMsg, expectedDashboardIdMSB, expectedDashboardIdLSB, expectedRuleChainIdMSB, expectedRuleChainIdLSB); + } + +} \ No newline at end of file diff --git a/application/src/test/java/org/thingsboard/server/service/install/InstallScriptsTest.java b/application/src/test/java/org/thingsboard/server/service/install/InstallScriptsTest.java new file mode 100644 index 0000000000..26eead0ec7 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/install/InstallScriptsTest.java @@ -0,0 +1,119 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.install; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.dao.dashboard.DashboardService; +import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService; +import org.thingsboard.server.dao.resource.ResourceService; +import org.thingsboard.server.dao.rule.RuleChainService; +import org.thingsboard.server.dao.service.validator.RuleChainDataValidator; +import org.thingsboard.server.dao.tenant.TenantService; +import org.thingsboard.server.dao.usagerecord.ApiLimitService; +import org.thingsboard.server.dao.widget.WidgetTypeService; +import org.thingsboard.server.dao.widget.WidgetsBundleService; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.willReturn; + +@Slf4j +@SpringBootTest(classes = {InstallScripts.class, RuleChainDataValidator.class}) +class InstallScriptsTest { + + @MockBean + RuleChainService ruleChainService; + @MockBean + DashboardService dashboardService; + @MockBean + WidgetTypeService widgetTypeService; + @MockBean + WidgetsBundleService widgetsBundleService; + @MockBean + OAuth2ConfigTemplateService oAuth2TemplateService; + @MockBean + ResourceService resourceService; + @SpyBean + InstallScripts installScripts; + + @MockBean + TenantService tenantService; + @MockBean + ApiLimitService apiLimitService; + @SpyBean + RuleChainDataValidator ruleChainValidator; + TenantId tenantId = TenantId.fromUUID(UUID.fromString("9ef79cdf-37a8-4119-b682-2e7ed4e018da")); + + @BeforeEach + void setUp() { + willReturn(true).given(tenantService).tenantExists(tenantId); + willReturn(true).given(apiLimitService).checkEntitiesLimit(any(), any()); + } + + @Test + void testDefaultRuleChainsTemplates() throws IOException { + Path dir = installScripts.getTenantRuleChainsDir(); + installScripts.findRuleChainsFromPath(dir) + .forEach(this::validateRuleChainTemplate); + } + + @Test + void testDefaultEdgeRuleChainsTemplates() throws IOException { + Path dir = installScripts.getEdgeRuleChainsDir(); + installScripts.findRuleChainsFromPath(dir) + .forEach(this::validateRuleChainTemplate); + } + + @Test + void testDeviceProfileDefaultRuleChainTemplate() { + validateRuleChainTemplate(installScripts.getDeviceProfileDefaultRuleChainTemplateFilePath()); + } + + private void validateRuleChainTemplate(Path templateFilePath) { + log.warn("validateRuleChainTemplate {}", templateFilePath); + JsonNode ruleChainJson = JacksonUtil.toJsonNode(templateFilePath.toFile()); + + RuleChain ruleChain = JacksonUtil.treeToValue(ruleChainJson.get("ruleChain"), RuleChain.class); + ruleChain.setTenantId(tenantId); + ruleChainValidator.validate(ruleChain, RuleChain::getTenantId); + ruleChain.setId(new RuleChainId(UUID.randomUUID())); + + RuleChainMetaData ruleChainMetaData = JacksonUtil.treeToValue(ruleChainJson.get("metadata"), RuleChainMetaData.class); + ruleChainMetaData.setRuleChainId(ruleChain.getId()); + List throwables = RuleChainDataValidator.validateMetaData(ruleChainMetaData); + + assertThat(throwables).as("templateFilePath " + templateFilePath) + .containsExactlyInAnyOrderElementsOf(Collections.emptyList()); + } + +} diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index 78614911d4..60dfcae1de 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -975,6 +975,8 @@ message ToCoreMsg { EdgeNotificationMsgProto edgeNotificationMsg = 5; DeviceActivityProto deviceActivityMsg = 6; NotificationSchedulerServiceMsg notificationSchedulerServiceMsg = 7; + LifecycleEventProto lifecycleEventMsg = 8; + ErrorEventProto errorEventMsg = 9; } /* High priority messages with low latency are handled by ThingsBoard Core Service separately */ @@ -1066,3 +1068,24 @@ message NotificationSchedulerServiceMsg { message NotificationRuleProcessorMsg { bytes trigger = 1; } + +message ErrorEventProto { + int64 tenantIdMSB = 1; + int64 tenantIdLSB = 2; + int64 entityIdMSB = 3; + int64 entityIdLSB = 4; + string serviceId = 5; + string method = 6; + string error = 7; +} + +message LifecycleEventProto { + int64 tenantIdMSB = 1; + int64 tenantIdLSB = 2; + int64 entityIdMSB = 3; + int64 entityIdLSB = 4; + string serviceId = 5; + string lcEventType = 6; + bool success = 7; + string error = 8; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java b/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java index 7360f9b8ab..449f252411 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java @@ -163,6 +163,15 @@ public class StringUtils { return false; } + public static boolean equalsAnyIgnoreCase(String string, String... otherStrings) { + for (String otherString : otherStrings) { + if (equalsIgnoreCase(string, otherString)) { + return true; + } + } + return false; + } + public static String substringAfterLast(String str, String sep) { return org.apache.commons.lang3.StringUtils.substringAfterLast(str, sep); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java index 14b62fe02e..6c89c91006 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java @@ -18,7 +18,6 @@ package org.thingsboard.server.common.data.device.profile; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; import java.util.List; @@ -45,8 +44,7 @@ public class SnmpDeviceProfileTransportConfiguration implements DeviceProfileTra private boolean isValid() { return timeoutMs != null && timeoutMs >= 0 && retries != null && retries >= 0 && communicationConfigs != null - && communicationConfigs.stream().allMatch(config -> config != null && config.isValid()) - && communicationConfigs.stream().flatMap(config -> config.getAllMappings().stream()).map(SnmpMapping::getOid) - .distinct().count() == communicationConfigs.stream().mapToInt(config -> config.getAllMappings().size()).sum(); + && communicationConfigs.stream().allMatch(config -> config != null && config.isValid()); } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/importing/csv/BulkImportColumnType.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/importing/csv/BulkImportColumnType.java index c004187876..87582b4d6d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/importing/csv/BulkImportColumnType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/importing/csv/BulkImportColumnType.java @@ -43,6 +43,10 @@ public enum BulkImportColumnType { LWM2M_SERVER_SECURITY_MODE("securityMode", LwM2MSecurityMode.NO_SEC.name()), LWM2M_SERVER_CLIENT_PUBLIC_KEY_OR_ID("clientPublicKeyOrId"), LWM2M_SERVER_CLIENT_SECRET_KEY("clientSecretKey"), + SNMP_HOST, + SNMP_PORT, + SNMP_VERSION, + SNMP_COMMUNITY_STRING, IS_GATEWAY, DESCRIPTION, ROUTING_KEY, diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java index a779d9a23d..1e0e5a745b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java @@ -15,11 +15,21 @@ */ package org.thingsboard.server.common.data.transport.snmp; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter public enum SnmpCommunicationSpec { - TELEMETRY_QUERYING, - CLIENT_ATTRIBUTES_QUERYING, - SHARED_ATTRIBUTES_SETTING, + TELEMETRY_QUERYING("telemetryQuerying"), + CLIENT_ATTRIBUTES_QUERYING("clientAttributesQuerying"), + + SHARED_ATTRIBUTES_SETTING("sharedAttributesSetting"), + TO_DEVICE_RPC_REQUEST("toDeviceRpcRequest"), + + TO_SERVER_RPC_REQUEST("toServerRpcRequest"); + + private final String label; - TO_DEVICE_RPC_REQUEST, } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMethod.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMethod.java index 257dd28217..e366cdb8b4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMethod.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMethod.java @@ -17,7 +17,8 @@ package org.thingsboard.server.common.data.transport.snmp; public enum SnmpMethod { GET(-96), - SET(-93); + SET(-93), + TRAP(-89); // codes taken from org.snmp4j.PDU class private final int code; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/SnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/SnmpCommunicationConfig.java index 36a7f64d3a..d09c9c147c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/SnmpCommunicationConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/SnmpCommunicationConfig.java @@ -37,7 +37,8 @@ import java.util.List; @Type(value = TelemetryQueryingSnmpCommunicationConfig.class, name = "TELEMETRY_QUERYING"), @Type(value = ClientAttributesQueryingSnmpCommunicationConfig.class, name = "CLIENT_ATTRIBUTES_QUERYING"), @Type(value = SharedAttributesSettingSnmpCommunicationConfig.class, name = "SHARED_ATTRIBUTES_SETTING"), - @Type(value = ToDeviceRpcRequestSnmpCommunicationConfig.class, name = "TO_DEVICE_RPC_REQUEST") + @Type(value = ToDeviceRpcRequestSnmpCommunicationConfig.class, name = "TO_DEVICE_RPC_REQUEST"), + @Type(value = ToServerRpcRequestSnmpCommunicationConfig.class, name = "TO_SERVER_RPC_REQUEST") }) public interface SnmpCommunicationConfig extends Serializable { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/ToServerRpcRequestSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/ToServerRpcRequestSnmpCommunicationConfig.java new file mode 100644 index 0000000000..f8a7968b82 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/ToServerRpcRequestSnmpCommunicationConfig.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.transport.snmp.config; + +import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; + +public class ToServerRpcRequestSnmpCommunicationConfig extends MultipleMappingsSnmpCommunicationConfig { + + private static final long serialVersionUID = 4851028734093214L; + + @Override + public SnmpCommunicationSpec getSpec() { + return SnmpCommunicationSpec.TO_SERVER_RPC_REQUEST; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/utils/TypeCastUtil.java b/common/data/src/main/java/org/thingsboard/server/common/data/util/TypeCastUtil.java similarity index 58% rename from application/src/main/java/org/thingsboard/server/utils/TypeCastUtil.java rename to common/data/src/main/java/org/thingsboard/server/common/data/util/TypeCastUtil.java index 3bf1efc8f4..cc40c0d13f 100644 --- a/application/src/main/java/org/thingsboard/server/utils/TypeCastUtil.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/util/TypeCastUtil.java @@ -13,35 +13,53 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.utils; +package org.thingsboard.server.common.data.util; import org.apache.commons.lang3.math.NumberUtils; +import org.apache.commons.lang3.tuple.Pair; import org.thingsboard.server.common.data.kv.DataType; import java.math.BigDecimal; -import java.util.Map; public class TypeCastUtil { private TypeCastUtil() {} - public static Map.Entry castValue(String value) { + public static Pair castValue(String value) { if (isNumber(value)) { String formattedValue = value.replace(',', '.'); try { BigDecimal bd = new BigDecimal(formattedValue); if (bd.stripTrailingZeros().scale() > 0 || isSimpleDouble(formattedValue)) { if (bd.scale() <= 16) { - return Map.entry(DataType.DOUBLE, bd.doubleValue()); + return Pair.of(DataType.DOUBLE, bd.doubleValue()); } } else { - return Map.entry(DataType.LONG, bd.longValueExact()); + return Pair.of(DataType.LONG, bd.longValueExact()); } } catch (RuntimeException ignored) {} } else if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) { - return Map.entry(DataType.BOOLEAN, Boolean.parseBoolean(value)); + return Pair.of(DataType.BOOLEAN, Boolean.parseBoolean(value)); + } + return Pair.of(DataType.STRING, value); + } + + public static Pair castToNumber(String value) { + if (isNumber(value)) { + String formattedValue = value.replace(',', '.'); + BigDecimal bd = new BigDecimal(formattedValue); + if (bd.stripTrailingZeros().scale() > 0 || isSimpleDouble(formattedValue)) { + if (bd.scale() <= 16) { + return Pair.of(DataType.DOUBLE, bd.doubleValue()); + } else { + return Pair.of(DataType.DOUBLE, bd); + } + } else { + return Pair.of(DataType.LONG, bd.longValueExact()); + } + } else { + throw new IllegalArgumentException("'" + value + "' can't be parsed as number"); } - return Map.entry(DataType.STRING, value); } private static boolean isNumber(String value) { diff --git a/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java b/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java index dcd543a9a6..35a4ab9cd5 100644 --- a/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java +++ b/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java @@ -111,7 +111,7 @@ public class EdgeGrpcClient implements EdgeRpcClient { .setConnectRequestMsg(ConnectRequestMsg.newBuilder() .setEdgeRoutingKey(edgeKey) .setEdgeSecret(edgeSecret) - .setEdgeVersion(EdgeVersion.V_3_4_0) + .setEdgeVersion(EdgeVersion.V_3_6_0) .setMaxInboundMessageSize(maxInboundMessageSize) .build()) .build()); diff --git a/common/edge-api/src/main/proto/edge.proto b/common/edge-api/src/main/proto/edge.proto index 3e8310222b..d9da289001 100644 --- a/common/edge-api/src/main/proto/edge.proto +++ b/common/edge-api/src/main/proto/edge.proto @@ -34,6 +34,7 @@ enum EdgeVersion { V_3_3_0 = 0; V_3_3_3 = 1; V_3_4_0 = 2; + V_3_6_0 = 3; } /** diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DateFormattingOptions.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DateFormattingOptions.java new file mode 100644 index 0000000000..0f4c5b625c --- /dev/null +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DateFormattingOptions.java @@ -0,0 +1,50 @@ +package org.thingsboard.script.api.tbel; + +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.StringUtils; + +import java.time.format.FormatStyle; +import java.util.TimeZone; + +@NoArgsConstructor +@Data +class DateFormattingOptions { + private static final TimeZone DEFAULT_TZ = TimeZone.getDefault(); + + private String timeZone; + private String dateStyle; + private String timeStyle; + @Getter + private String pattern; + + public DateFormattingOptions(String timeZone) { + this.timeZone = timeZone; + } + + TimeZone getTimeZone() { + return StringUtils.isNotEmpty(timeZone) ? TimeZone.getTimeZone(timeZone) : TimeZone.getDefault(); + } + + FormatStyle getDateStyle() { + return getFormatStyle(dateStyle, FormatStyle.SHORT); + } + + FormatStyle getTimeStyle() { + return getFormatStyle(timeStyle, FormatStyle.MEDIUM); + } + + private static FormatStyle getFormatStyle(String style, FormatStyle defaultStyle) { + if (StringUtils.isNotEmpty(style)) { + try { + return FormatStyle.valueOf(style.toUpperCase()); + } catch (IllegalArgumentException e) { + return defaultStyle; + } + } else { + return defaultStyle; + } + } + +} diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java index c22e2c329d..4b58deab43 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -15,6 +15,9 @@ */ package org.thingsboard.script.api.tbel; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.StringUtils; + import java.text.DateFormat; import java.text.SimpleDateFormat; import java.time.Instant; @@ -22,13 +25,16 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; +import java.time.chrono.IsoChronology; import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalAccessor; import java.util.Date; import java.util.GregorianCalendar; import java.util.Locale; -import java.util.TimeZone; +import java.util.function.BiFunction; +import java.util.function.Function; public class TbDate extends Date { @@ -77,15 +83,77 @@ public class TbDate extends Date { return isoDateFormat.get().format(this); } + public String toLocaleDateString() { + return toLocaleDateString(null, null); + } + + public String toLocaleDateString(String locale) { + return toLocaleDateString(locale, null); + } + + public String toLocaleDateString(String localeStr, String optionsStr) { + return toLocaleString(localeStr, optionsStr, (locale, options) -> DateTimeFormatter.ofLocalizedDate(options.getDateStyle()).withLocale(locale)); + } + + public String toLocaleTimeString() { + return toLocaleTimeString(null, null); + } + + public String toLocaleTimeString(String locale) { + return toLocaleTimeString(locale, null); + } + + public String toLocaleTimeString(String localeStr, String optionsStr) { + return toLocaleString(localeStr, optionsStr, (locale, options) -> DateTimeFormatter.ofLocalizedTime(options.getTimeStyle()).withLocale(locale)); + } + + @Override + public String toLocaleString() { + return toLocaleString(null, null); + } + public String toLocaleString(String locale) { - DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, Locale.forLanguageTag(locale)); - return formatter.format(this); + return toLocaleString(locale, null); } - public String toLocaleString(String locale, String tz) { - DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, Locale.forLanguageTag(locale)); - formatter.setTimeZone(TimeZone.getTimeZone(tz)); - return formatter.format(this); + public String toLocaleString(String localeStr, String optionsStr) { + return toLocaleString(localeStr, optionsStr, (locale, options) -> { + String formatPattern = + DateTimeFormatterBuilder.getLocalizedDateTimePattern( + options.getDateStyle(), + options.getTimeStyle(), + IsoChronology.INSTANCE, + locale); + return DateTimeFormatter.ofPattern(formatPattern, locale); + }); + } + + public String toLocaleString(String localeStr, String optionsStr, BiFunction formatterBuilder) { + Locale locale = StringUtils.isNotEmpty(localeStr) ? Locale.forLanguageTag(localeStr) : Locale.getDefault(); + DateFormattingOptions options = getDateFormattingOptions(optionsStr); + ZonedDateTime zdt = this.toInstant().atZone(options.getTimeZone().toZoneId()); + DateTimeFormatter formatter; + if (StringUtils.isNotEmpty(options.getPattern())) { + formatter = new DateTimeFormatterBuilder().appendPattern(options.getPattern()).toFormatter(locale); + } else { + formatter = formatterBuilder.apply(locale, options); + } + return formatter.format(zdt); + } + + private static DateFormattingOptions getDateFormattingOptions(String options) { + DateFormattingOptions opt = null; + if (StringUtils.isNotEmpty(options)) { + try { + opt = JacksonUtil.fromString(options, DateFormattingOptions.class); + } catch (IllegalArgumentException iae) { + opt = new DateFormattingOptions(options); + } + } + if (opt == null) { + opt = new DateFormattingOptions(); + } + return opt; } public static long now() { diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java index 88f1f825c1..87dd4f0ebf 100644 --- a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java +++ b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java @@ -21,12 +21,24 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; +import org.junit.Assert; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.thingsboard.common.util.JacksonUtil; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.chrono.IsoChronology; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.FormatStyle; import java.util.ArrayList; import java.util.Calendar; import java.util.List; +import java.util.Locale; +import java.util.TimeZone; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; @@ -124,4 +136,174 @@ class TbDateTest { .startsWith(datePrefix); } + @Test + void testToLocaleDateString() { + TbDate d = new TbDate(1693962245000L); + + // Depends on time zone, so we just check it works; + Assert.assertNotNull(d.toLocaleDateString()); + Assert.assertNotNull(d.toLocaleDateString("en-US")); + + Assert.assertEquals("9/5/23", d.toLocaleDateString("en-US", "America/New_York")); + Assert.assertEquals("23. 9. 5.", d.toLocaleDateString("ko-KR", "America/New_York")); + Assert.assertEquals("06.09.23", d.toLocaleDateString( "uk-UA", "Europe/Kiev")); + Assert.assertEquals("5\u200F/9\u200F/2023", d.toLocaleDateString( "ar-EG", "America/New_York")); + + Assert.assertEquals("Tuesday, September 5, 2023", d.toLocaleDateString("en-US", JacksonUtil.newObjectNode() + .put("timeZone", "America/New_York") + .put("dateStyle", "full") + .toString())); + Assert.assertEquals("2023년 9월 5일 화요일", d.toLocaleDateString("ko-KR", JacksonUtil.newObjectNode() + .put("timeZone", "America/New_York") + .put("dateStyle", "full") + .toString())); + Assert.assertEquals("середа, 6 вересня 2023 р.", d.toLocaleDateString("uk-UA", JacksonUtil.newObjectNode() + .put("timeZone", "Europe/Kiev") + .put("dateStyle", "full") + .toString())); + Assert.assertEquals("الثلاثاء، 5 سبتمبر 2023", d.toLocaleDateString("ar-EG", JacksonUtil.newObjectNode() + .put("timeZone", "America/New_York") + .put("dateStyle", "full") + .toString())); + + Assert.assertEquals("Tuesday 9/5/2023", d.toLocaleDateString("en-US", JacksonUtil.newObjectNode() + .put("timeZone", "America/New_York") + .put("pattern", "EEEE M/d/yyyy") + .toString())); + Assert.assertEquals("화요일 9/5/2023", d.toLocaleDateString("ko-KR", JacksonUtil.newObjectNode() + .put("timeZone", "America/New_York") + .put("pattern", "EEEE M/d/yyyy") + .toString())); + Assert.assertEquals("середа 9/6/2023", d.toLocaleDateString("uk-UA", JacksonUtil.newObjectNode() + .put("timeZone", "Europe/Kiev") + .put("pattern", "EEEE M/d/yyyy") + .toString())); + Assert.assertEquals("الثلاثاء 9/5/2023", d.toLocaleDateString("ar-EG", JacksonUtil.newObjectNode() + .put("timeZone", "America/New_York") + .put("pattern", "EEEE M/d/yyyy") + .toString())); + } + + @Test + void testToLocaleTimeString() { + TbDate d = new TbDate(1693962245000L); + + // Depends on time zone, so we just check it works; + Assert.assertNotNull(d.toLocaleTimeString()); + Assert.assertNotNull(d.toLocaleTimeString("en-US")); + + + Assert.assertEquals("9:04:05 PM", d.toLocaleTimeString("en-US", "America/New_York")); + Assert.assertEquals("오후 9:04:05", d.toLocaleTimeString("ko-KR", "America/New_York")); + Assert.assertEquals("04:04:05", d.toLocaleTimeString( "uk-UA", "Europe/Kiev")); + Assert.assertEquals("9:04:05 م", d.toLocaleTimeString( "ar-EG", "America/New_York")); + + Assert.assertEquals("9:04:05 PM Eastern Daylight Time", d.toLocaleTimeString("en-US", JacksonUtil.newObjectNode() + .put("timeZone", "America/New_York") + .put("timeStyle", "full") + .toString())); + Assert.assertEquals("오후 9시 4분 5초 미 동부 하계 표준시", d.toLocaleTimeString("ko-KR", JacksonUtil.newObjectNode() + .put("timeZone", "America/New_York") + .put("timeStyle", "full") + .toString())); + Assert.assertEquals("04:04:05 за східноєвропейським літнім часом", d.toLocaleTimeString("uk-UA", JacksonUtil.newObjectNode() + .put("timeZone", "Europe/Kiev") + .put("timeStyle", "full") + .toString())); + Assert.assertEquals("9:04:05 م التوقيت الصيفي الشرقي لأمريكا الشمالية", d.toLocaleTimeString("ar-EG", JacksonUtil.newObjectNode() + .put("timeZone", "America/New_York") + .put("timeStyle", "full") + .toString())); + + Assert.assertEquals("9:04:05 PM", d.toLocaleTimeString("en-US", JacksonUtil.newObjectNode() + .put("timeZone", "America/New_York") + .put("pattern", "h:mm:ss a") + .toString())); + Assert.assertEquals("9:04:05 오후", d.toLocaleTimeString("ko-KR", JacksonUtil.newObjectNode() + .put("timeZone", "America/New_York") + .put("pattern", "h:mm:ss a") + .toString())); + Assert.assertEquals("4:04:05 дп", d.toLocaleTimeString("uk-UA", JacksonUtil.newObjectNode() + .put("timeZone", "Europe/Kiev") + .put("pattern", "h:mm:ss a") + .toString())); + Assert.assertEquals("9:04:05 م", d.toLocaleTimeString("ar-EG", JacksonUtil.newObjectNode() + .put("timeZone", "America/New_York") + .put("pattern", "h:mm:ss a") + .toString())); + } + + @Test + void testToLocaleString() { + TbDate d = new TbDate(1693962245000L); + + // Depends on time zone, so we just check it works; + Assert.assertNotNull(d.toLocaleString()); + Assert.assertNotNull(d.toLocaleString("en-US")); + + Assert.assertEquals("9/5/23, 9:04:05 PM", d.toLocaleString("en-US", "America/New_York")); + Assert.assertEquals("23. 9. 5. 오후 9:04:05", d.toLocaleString("ko-KR", "America/New_York")); + Assert.assertEquals("06.09.23, 04:04:05", d.toLocaleString( "uk-UA", "Europe/Kiev")); + Assert.assertEquals("5\u200F/9\u200F/2023 9:04:05 م", d.toLocaleString( "ar-EG", "America/New_York")); + + Assert.assertEquals("Tuesday, September 5, 2023 at 9:04:05 PM Eastern Daylight Time", d.toLocaleString("en-US", JacksonUtil.newObjectNode() + .put("timeZone", "America/New_York") + .put("dateStyle", "full") + .put("timeStyle", "full") + .toString())); + Assert.assertEquals("2023년 9월 5일 화요일 오후 9시 4분 5초 미 동부 하계 표준시", d.toLocaleString("ko-KR", JacksonUtil.newObjectNode() + .put("timeZone", "America/New_York") + .put("dateStyle", "full") + .put("timeStyle", "full") + .toString())); + Assert.assertEquals("середа, 6 вересня 2023 р. о 04:04:05 за східноєвропейським літнім часом", d.toLocaleString("uk-UA", JacksonUtil.newObjectNode() + .put("timeZone", "Europe/Kiev") + .put("dateStyle", "full") + .put("timeStyle", "full") + .toString())); + Assert.assertEquals("الثلاثاء، 5 سبتمبر 2023 9:04:05 م التوقيت الصيفي الشرقي لأمريكا الشمالية", d.toLocaleString("ar-EG", JacksonUtil.newObjectNode() + .put("timeZone", "America/New_York") + .put("dateStyle", "full") + .put("timeStyle", "full") + .toString())); + + Assert.assertEquals("9/5/2023, 9:04:05 PM", d.toLocaleString("en-US", JacksonUtil.newObjectNode() + .put("timeZone", "America/New_York") + .put("pattern", "M/d/yyyy, h:mm:ss a") + .toString())); + Assert.assertEquals("9/5/2023, 9:04:05 오후", d.toLocaleString("ko-KR", JacksonUtil.newObjectNode() + .put("timeZone", "America/New_York") + .put("pattern", "M/d/yyyy, h:mm:ss a") + .toString())); + Assert.assertEquals("9/6/2023, 4:04:05 дп", d.toLocaleString("uk-UA", JacksonUtil.newObjectNode() + .put("timeZone", "Europe/Kiev") + .put("pattern", "M/d/yyyy, h:mm:ss a") + .toString())); + Assert.assertEquals("9/5/2023, 9:04:05 م", d.toLocaleString("ar-EG", JacksonUtil.newObjectNode() + .put("timeZone", "America/New_York") + .put("pattern", "M/d/yyyy, h:mm:ss a") + .toString())); + } + + private static String toLocalString(TbDate d, Locale locale, ZoneId tz) { + LocalDateTime ldt = d.toInstant().atZone(tz).toLocalDateTime(); + +// new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter(locale) + + String formatPattern = + DateTimeFormatterBuilder.getLocalizedDateTimePattern( + FormatStyle.SHORT, + FormatStyle.MEDIUM, + IsoChronology.INSTANCE, + locale); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(formatPattern, locale); + return formatter.format(ldt); + } + + private static String toLocalString2(TbDate d, Locale locale, ZoneId tz) { + DateFormat dateFormat = SimpleDateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, locale); + dateFormat.setTimeZone(TimeZone.getTimeZone(tz)); + return dateFormat.format(d); + } + } diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java index 5dcffbf62f..f3c6d8bc83 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfigu import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.transport.DeviceUpdatedEvent; @@ -113,19 +114,25 @@ public class SnmpTransportContext extends TransportContext { SnmpDeviceProfileTransportConfiguration profileTransportConfiguration = (SnmpDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); SnmpDeviceTransportConfiguration deviceTransportConfiguration = (SnmpDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); - DeviceSessionContext deviceSessionContext; + DeviceSessionContext sessionContext; try { - deviceSessionContext = new DeviceSessionContext( - device, deviceProfile, credentials.getCredentialsId(), - profileTransportConfiguration, deviceTransportConfiguration, this - ); - registerSessionMsgListener(deviceSessionContext); + sessionContext = DeviceSessionContext.builder() + .tenantId(deviceProfile.getTenantId()) + .device(device) + .deviceProfile(deviceProfile) + .token(credentials.getCredentialsId()) + .profileTransportConfiguration(profileTransportConfiguration) + .deviceTransportConfiguration(deviceTransportConfiguration) + .snmpTransportContext(this) + .build(); + registerSessionMsgListener(sessionContext); } catch (Exception e) { log.error("Failed to establish session for SNMP device {}: {}", device.getId(), e.toString()); + transportService.errorEvent(device.getTenantId(), device.getId(), "sessionEstablishing", e); return; } - sessions.put(device.getId(), deviceSessionContext); - snmpTransportService.createQueryingTasks(deviceSessionContext); + sessions.put(device.getId(), sessionContext); + snmpTransportService.createQueryingTasks(sessionContext); log.info("Established SNMP device session for device {}", device.getId()); } @@ -148,14 +155,17 @@ public class SnmpTransportContext extends TransportContext { sessionContext.initializeTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration); snmpTransportService.cancelQueryingTasks(sessionContext); snmpTransportService.createQueryingTasks(sessionContext); + transportService.lifecycleEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), ComponentLifecycleEvent.UPDATED, true, null); } else if (!newDeviceTransportConfiguration.equals(sessionContext.getDeviceTransportConfiguration())) { sessionContext.setDeviceTransportConfiguration(newDeviceTransportConfiguration); sessionContext.initializeTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration); + transportService.lifecycleEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), ComponentLifecycleEvent.UPDATED, true, null); } else { log.trace("Configuration of the device {} was not updated", device); } } catch (Exception e) { log.error("Failed to update session for SNMP device {}: {}", sessionContext.getDeviceId(), e.getMessage()); + transportService.lifecycleEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), ComponentLifecycleEvent.UPDATED, false, e); destroyDeviceSession(sessionContext); } } @@ -168,43 +178,58 @@ public class SnmpTransportContext extends TransportContext { transportService.deregisterSession(sessionContext.getSessionInfo()); snmpTransportService.cancelQueryingTasks(sessionContext); sessions.remove(sessionContext.getDeviceId()); + transportService.lifecycleEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), ComponentLifecycleEvent.STOPPED, true, null); log.trace("Unregistered and removed session"); } - private void registerSessionMsgListener(DeviceSessionContext deviceSessionContext) { + private void registerSessionMsgListener(DeviceSessionContext sessionContext) { transportService.process(DeviceTransportType.SNMP, - TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceSessionContext.getToken()).build(), + TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(sessionContext.getToken()).build(), new TransportServiceCallback<>() { @Override public void onSuccess(ValidateDeviceCredentialsResponse msg) { if (msg.hasDeviceInfo()) { - SessionInfoProto sessionInfo = SessionInfoCreator.create( - msg, SnmpTransportContext.this, UUID.randomUUID() - ); - - transportService.registerAsyncSession(sessionInfo, deviceSessionContext); - transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), TransportServiceCallback.EMPTY); - transportService.process(sessionInfo, TransportProtos.SubscribeToRPCMsg.newBuilder().build(), TransportServiceCallback.EMPTY); - - deviceSessionContext.setSessionInfo(sessionInfo); - deviceSessionContext.setDeviceInfo(msg.getDeviceInfo()); - deviceSessionContext.setConnected(true); + registerTransportSession(sessionContext, msg); + sessionContext.setSessionTimeoutHandler(() -> { + registerTransportSession(sessionContext, msg); + }); + transportService.lifecycleEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), ComponentLifecycleEvent.STARTED, true, null); } else { - log.warn("[{}] Failed to process device auth", deviceSessionContext.getDeviceId()); + log.warn("[{}] Failed to process device auth", sessionContext.getDeviceId()); } } @Override public void onError(Throwable e) { - log.warn("[{}] Failed to process device auth: {}", deviceSessionContext.getDeviceId(), e); + log.warn("[{}] Failed to process device auth: {}", sessionContext.getDeviceId(), e); + transportService.lifecycleEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), ComponentLifecycleEvent.STARTED, false, e); } }); } + private void registerTransportSession(DeviceSessionContext deviceSessionContext, ValidateDeviceCredentialsResponse msg) { + SessionInfoProto sessionInfo = SessionInfoCreator.create( + msg, SnmpTransportContext.this, UUID.randomUUID() + ); + log.debug("Registering transport session: {}", sessionInfo); + + transportService.registerAsyncSession(sessionInfo, deviceSessionContext); + transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder() + .setSessionType(TransportProtos.SessionType.ASYNC) + .build(), TransportServiceCallback.EMPTY); + transportService.process(sessionInfo, TransportProtos.SubscribeToRPCMsg.newBuilder() + .setSessionType(TransportProtos.SessionType.ASYNC) + .build(), TransportServiceCallback.EMPTY); + + deviceSessionContext.setSessionInfo(sessionInfo); + deviceSessionContext.setDeviceInfo(msg.getDeviceInfo()); + deviceSessionContext.setConnected(true); + } + @EventListener(DeviceUpdatedEvent.class) public void onDeviceUpdatedOrCreated(DeviceUpdatedEvent deviceUpdatedEvent) { Device device = deviceUpdatedEvent.getDevice(); - log.trace("Got creating or updating device event for device {}", device); + log.debug("Got creating or updating device event for device {}", device); DeviceTransportType transportType = Optional.ofNullable(device.getDeviceData().getTransportConfiguration()) .map(DeviceTransportConfiguration::getType) .orElse(null); @@ -238,6 +263,7 @@ public class SnmpTransportContext extends TransportContext { } public void onDeviceProfileUpdated(DeviceProfile deviceProfile, DeviceSessionContext sessionContext) { + log.debug("Handling device profile {} update event for device {}", deviceProfile.getId(), sessionContext.getDeviceId()); updateDeviceSession(sessionContext, sessionContext.getDevice(), deviceProfile); } diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduService.java index b395672c00..1cfcfd2c85 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduService.java @@ -15,7 +15,9 @@ */ package org.thingsboard.server.transport.snmp.service; +import com.google.common.collect.Lists; import com.google.gson.JsonObject; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.snmp4j.PDU; import org.snmp4j.ScopedPDU; @@ -25,43 +27,61 @@ import org.snmp4j.smi.OID; import org.snmp4j.smi.OctetString; import org.snmp4j.smi.Variable; import org.snmp4j.smi.VariableBinding; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion; import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; +import org.thingsboard.server.common.data.util.TypeCastUtil; import org.thingsboard.server.queue.util.TbSnmpTransportComponent; import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; -import java.util.stream.IntStream; @TbSnmpTransportComponent @Service @Slf4j +@RequiredArgsConstructor public class PduService { - public PDU createPdu(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map values) { - PDU pdu = setUpPdu(sessionContext); - pdu.setType(communicationConfig.getMethod().getCode()); - pdu.addAll(communicationConfig.getAllMappings().stream() - .filter(mapping -> values.isEmpty() || values.containsKey(mapping.getKey())) - .map(mapping -> Optional.ofNullable(values.get(mapping.getKey())) - .map(value -> { - Variable variable = toSnmpVariable(value, mapping.getDataType()); - return new VariableBinding(new OID(mapping.getOid()), variable); - }) - .orElseGet(() -> new VariableBinding(new OID(mapping.getOid())))) - .collect(Collectors.toList())); + @Value("${transport.snmp.max_request_oids:100}") + private int maxRequestOids; + + @Value("${transport.snmp.response.ignore_type_cast_errors:false}") + private boolean ignoreTypeCastErrors; + + public List createPdus(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map values) { + List pdus = new ArrayList<>(); + List allMappings = communicationConfig.getAllMappings(); + + for (List mappings : Lists.partition(allMappings, maxRequestOids)) { + PDU pdu = setUpPdu(sessionContext); + pdu.setType(communicationConfig.getMethod().getCode()); + pdu.addAll(mappings.stream() + .filter(mapping -> values.isEmpty() || values.containsKey(mapping.getKey())) + .map(mapping -> Optional.ofNullable(values.get(mapping.getKey())) + .map(value -> { + Variable variable = toSnmpVariable(value, mapping.getDataType()); + return new VariableBinding(new OID(mapping.getOid()), variable); + }) + .orElseGet(() -> new VariableBinding(new OID(mapping.getOid())))) + .collect(Collectors.toList())); + if (pdu.size() > 0) { + pdus.add(pdu); + } + } - return pdu; + return pdus; } public PDU createSingleVariablePdu(DeviceSessionContext sessionContext, SnmpMethod snmpMethod, String oid, String value, DataType dataType) { @@ -116,8 +136,8 @@ public class PduService { } - public JsonObject processPdu(PDU pdu, List responseMappings) { - Map values = processPdu(pdu); + public JsonObject processPdus(List pdus, List responseMappings) { + Map values = processPdus(pdus); Map mappings = new HashMap<>(); if (responseMappings != null) { @@ -143,29 +163,40 @@ public class PduService { return data; } - public Map processPdu(PDU pdu) { - return IntStream.range(0, pdu.size()) - .mapToObj(pdu::get) + public Map processPdus(List pdus) { + return pdus.stream() + .flatMap(pdu -> pdu.getVariableBindings().stream()) .filter(Objects::nonNull) .filter(variableBinding -> !(variableBinding.getVariable() instanceof Null)) .collect(Collectors.toMap(VariableBinding::getOid, VariableBinding::toValueString)); } public void processValue(String key, DataType dataType, String value, JsonObject result) { - switch (dataType) { - case LONG: - result.addProperty(key, Long.parseLong(value)); - break; - case BOOLEAN: - result.addProperty(key, Boolean.parseBoolean(value)); - break; - case DOUBLE: - result.addProperty(key, Double.parseDouble(value)); - break; - case STRING: - case JSON: - default: - result.addProperty(key, value); + try { + switch (dataType) { + case STRING: + case JSON: + result.addProperty(key, value); + break; + case LONG: + case DOUBLE: + result.addProperty(key, TypeCastUtil.castToNumber(value).getValue()); + break; + case BOOLEAN: + if (StringUtils.equalsAnyIgnoreCase(value, "true", "false")) { + result.addProperty(key, Boolean.parseBoolean(value)); + } else { + throw new IllegalArgumentException("Can't parse '" + value + "' as boolean"); + } + break; + } + } catch (IllegalArgumentException e) { + if (ignoreTypeCastErrors) { + log.debug("Ignoring value '{}' for key '{}' because of data type mismatch ({} required)", value, key, dataType); + } else { + throw e; + } } } + } diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java index 6e68a0bd44..0b60db68b1 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java @@ -17,10 +17,13 @@ package org.thingsboard.server.transport.snmp.service; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import lombok.Builder; import lombok.Data; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.snmp4j.CommandResponder; +import org.snmp4j.CommandResponderEvent; import org.snmp4j.PDU; import org.snmp4j.Snmp; import org.snmp4j.TransportMapping; @@ -29,10 +32,15 @@ import org.snmp4j.mp.MPv3; import org.snmp4j.security.SecurityModels; import org.snmp4j.security.SecurityProtocols; import org.snmp4j.security.USM; +import org.snmp4j.smi.Address; import org.snmp4j.smi.OctetString; +import org.snmp4j.smi.TcpAddress; +import org.snmp4j.smi.UdpAddress; import org.snmp4j.transport.DefaultTcpTransportMapping; import org.snmp4j.transport.DefaultUdpTransportMapping; +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.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardThreadFactory; @@ -45,15 +53,16 @@ import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; import org.thingsboard.server.common.data.transport.snmp.config.RepeatingQueryingSnmpCommunicationConfig; import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; import org.thingsboard.server.common.transport.TransportService; -import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbSnmpTransportComponent; +import org.thingsboard.server.transport.snmp.SnmpTransportContext; import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.EnumMap; @@ -71,9 +80,11 @@ import java.util.stream.Collectors; @Service @Slf4j @RequiredArgsConstructor -public class SnmpTransportService implements TbTransportService { +public class SnmpTransportService implements TbTransportService, CommandResponder { private final TransportService transportService; private final PduService pduService; + @Autowired @Lazy + private SnmpTransportContext transportContext; @Getter private Snmp snmp; @@ -83,6 +94,8 @@ public class SnmpTransportService implements TbTransportService { private final Map responseDataMappers = new EnumMap<>(SnmpCommunicationSpec.class); private final Map responseProcessors = new EnumMap<>(SnmpCommunicationSpec.class); + @Value("${transport.snmp.bind_port:1620}") + private Integer snmpBindPort; @Value("${transport.snmp.response_processing.parallelism_level}") private Integer responseProcessingParallelismLevel; @Value("${transport.snmp.underlying_protocol}") @@ -114,15 +127,16 @@ public class SnmpTransportService implements TbTransportService { TransportMapping transportMapping; switch (snmpUnderlyingProtocol) { case "udp": - transportMapping = new DefaultUdpTransportMapping(); + transportMapping = new DefaultUdpTransportMapping(new UdpAddress(snmpBindPort)); break; case "tcp": - transportMapping = new DefaultTcpTransportMapping(); + transportMapping = new DefaultTcpTransportMapping(new TcpAddress(snmpBindPort)); break; default: throw new IllegalArgumentException("Underlying protocol " + snmpUnderlyingProtocol + " for SNMP is not supported"); } snmp = new Snmp(transportMapping); + snmp.addNotificationListener(transportMapping, transportMapping.getListenAddress(), this); snmp.listen(); USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0); @@ -143,6 +157,7 @@ public class SnmpTransportService implements TbTransportService { } } catch (Exception e) { log.error("Failed to send SNMP request for device {}: {}", sessionContext.getDeviceId(), e.toString()); + transportService.errorEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), config.getSpec().getLabel(), e); } }, queryingFrequency, queryingFrequency, TimeUnit.MILLISECONDS); }) @@ -161,18 +176,24 @@ public class SnmpTransportService implements TbTransportService { } private void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map values) { - PDU request = pduService.createPdu(sessionContext, communicationConfig, values); - RequestInfo requestInfo = new RequestInfo(communicationConfig.getSpec(), communicationConfig.getAllMappings()); - sendRequest(sessionContext, request, requestInfo); + List request = pduService.createPdus(sessionContext, communicationConfig, values); + RequestContext requestContext = RequestContext.builder() + .communicationSpec(communicationConfig.getSpec()) + .method(communicationConfig.getMethod()) + .responseMappings(communicationConfig.getAllMappings()) + .requestSize(request.size()) + .build(); + sendRequest(sessionContext, request, requestContext); } - private void sendRequest(DeviceSessionContext sessionContext, PDU request, RequestInfo requestInfo) { - if (request.size() > 0) { - log.trace("Executing SNMP request for device {}. Variables bindings: {}", sessionContext.getDeviceId(), request.getVariableBindings()); + private void sendRequest(DeviceSessionContext sessionContext, List request, RequestContext requestContext) { + for (PDU pdu : request) { + log.debug("Executing SNMP request for device {} with {} variable bindings", sessionContext.getDeviceId(), pdu.size()); try { - snmp.send(request, sessionContext.getTarget(), requestInfo, sessionContext); + snmp.send(pdu, sessionContext.getTarget(), requestContext, sessionContext); } catch (IOException e) { log.error("Failed to send SNMP request to device {}: {}", sessionContext.getDeviceId(), e.toString()); + transportService.errorEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), requestContext.getCommunicationSpec().getLabel(), e); } } } @@ -215,51 +236,135 @@ public class SnmpTransportService implements TbTransportService { DataType dataType = snmpMapping.getDataType(); PDU request = pduService.createSingleVariablePdu(sessionContext, snmpMethod, oid, value, dataType); - RequestInfo requestInfo = new RequestInfo(toDeviceRpcRequestMsg.getRequestId(), communicationConfig.getSpec(), communicationConfig.getAllMappings()); - sendRequest(sessionContext, request, requestInfo); + RequestContext requestContext = RequestContext.builder() + .requestId(toDeviceRpcRequestMsg.getRequestId()) + .communicationSpec(communicationConfig.getSpec()) + .method(snmpMethod) + .responseMappings(communicationConfig.getAllMappings()) + .requestSize(1) + .build(); + sendRequest(sessionContext, List.of(request), requestContext); } public void processResponseEvent(DeviceSessionContext sessionContext, ResponseEvent event) { ((Snmp) event.getSource()).cancel(event.getRequest(), sessionContext); - + RequestContext requestContext = (RequestContext) event.getUserObject(); if (event.getError() != null) { log.warn("SNMP response error: {}", event.getError().toString()); + transportService.errorEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), requestContext.getCommunicationSpec().getLabel(), new RuntimeException(event.getError())); return; } - PDU response = event.getResponse(); - if (response == null) { - log.debug("No response from SNMP device {}, requestId: {}", sessionContext.getDeviceId(), event.getRequest().getRequestID()); + PDU responsePdu = event.getResponse(); + if (log.isTraceEnabled()) { + log.trace("Received PDU for device {}: {}", sessionContext.getDeviceId(), responsePdu); + } + + List response; + if (requestContext.getRequestSize() == 1) { + if (responsePdu == null) { + log.debug("No response from SNMP device {}, requestId: {}", sessionContext.getDeviceId(), event.getRequest().getRequestID()); + if (requestContext.getMethod() == SnmpMethod.GET) { + transportService.errorEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), requestContext.getCommunicationSpec().getLabel(), new RuntimeException("No response from device")); + } + return; + } + response = List.of(responsePdu); + } else { + List responseParts = requestContext.getResponseParts(); + responseParts.add(responsePdu); + if (responseParts.size() == requestContext.getRequestSize()) { + response = new ArrayList<>(); + for (PDU responsePart : responseParts) { + if (responsePart != null) { + response.add(responsePart); + } + } + log.debug("All response parts are collected for request to device {}", sessionContext.getDeviceId()); + } else { + log.trace("Awaiting other response parts for request to device {}", sessionContext.getDeviceId()); + return; + } + } + + responseProcessingExecutor.execute(() -> { + try { + processResponse(sessionContext, response, requestContext); + } catch (Exception e) { + transportService.errorEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), requestContext.getCommunicationSpec().getLabel(), e); + } + }); + } + + /* + * SNMP notifications handler + * + * TODO: add check for host uniqueness when saving device (for backward compatibility - only for the ones using from-device RPC requests) + * + * NOTE: SNMP TRAPs support won't work properly when there is more than one SNMP transport, + * due to load-balancing of requests from devices: session might not be on this instance + * */ + @Override + public void processPdu(CommandResponderEvent event) { + Address sourceAddress = event.getPeerAddress(); + DeviceSessionContext sessionContext = transportContext.getSessions().stream() + .filter(session -> session.getTarget().getAddress().equals(sourceAddress)) + .findFirst().orElse(null); + if (sessionContext == null) { + log.warn("SNMP TRAP processing failed: couldn't find device session for address {}", sourceAddress); return; } - RequestInfo requestInfo = (RequestInfo) event.getUserObject(); + try { + processIncomingTrap(sessionContext, event); + } catch (Throwable e) { + transportService.errorEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), + SnmpCommunicationSpec.TO_SERVER_RPC_REQUEST.getLabel(), e); + } + } + + private void processIncomingTrap(DeviceSessionContext sessionContext, CommandResponderEvent event) { + PDU pdu = event.getPDU(); + if (pdu == null) { + log.warn("Got empty trap from device {}", sessionContext.getDeviceId()); + throw new IllegalArgumentException("Received TRAP with no data"); + } + + log.debug("Processing SNMP trap from device {} (PDU: {}}", sessionContext.getDeviceId(), pdu); + SnmpCommunicationConfig communicationConfig = sessionContext.getProfileTransportConfiguration().getCommunicationConfigs().stream() + .filter(config -> config.getSpec() == SnmpCommunicationSpec.TO_SERVER_RPC_REQUEST).findFirst() + .orElseThrow(() -> new IllegalArgumentException("No config found for to-server RPC requests")); + RequestContext requestContext = RequestContext.builder() + .communicationSpec(communicationConfig.getSpec()) + .responseMappings(communicationConfig.getAllMappings()) + .method(SnmpMethod.TRAP) + .build(); + responseProcessingExecutor.execute(() -> { - processResponse(sessionContext, response, requestInfo); + processResponse(sessionContext, List.of(pdu), requestContext); }); } - private void processResponse(DeviceSessionContext sessionContext, PDU response, RequestInfo requestInfo) { - ResponseProcessor responseProcessor = responseProcessors.get(requestInfo.getCommunicationSpec()); + private void processResponse(DeviceSessionContext sessionContext, List response, RequestContext requestContext) { + ResponseProcessor responseProcessor = responseProcessors.get(requestContext.getCommunicationSpec()); if (responseProcessor == null) return; - JsonObject responseData = responseDataMappers.get(requestInfo.getCommunicationSpec()).map(response, requestInfo); - - if (responseData.entrySet().isEmpty()) { - log.debug("No values is the SNMP response for device {}. Request id: {}", sessionContext.getDeviceId(), response.getRequestID()); - return; + JsonObject responseData = responseDataMappers.get(requestContext.getCommunicationSpec()).map(response, requestContext); + if (responseData.size() == 0) { + log.warn("No values in the SNMP response for device {}", sessionContext.getDeviceId()); + throw new IllegalArgumentException("No values in the response"); } - responseProcessor.process(responseData, requestInfo, sessionContext); + responseProcessor.process(responseData, requestContext, sessionContext); reportActivity(sessionContext.getSessionInfo()); } private void configureResponseDataMappers() { - responseDataMappers.put(SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST, (pdu, requestInfo) -> { + responseDataMappers.put(SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST, (pdus, requestContext) -> { JsonObject responseData = new JsonObject(); - pduService.processPdu(pdu).forEach((oid, value) -> { - requestInfo.getResponseMappings().stream() + pduService.processPdus(pdus).forEach((oid, value) -> { + requestContext.getResponseMappings().stream() .filter(snmpMapping -> snmpMapping.getOid().equals(oid.toDottedString())) .findFirst() .ifPresent(snmpMapping -> { @@ -269,8 +374,8 @@ public class SnmpTransportService implements TbTransportService { return responseData; }); - ResponseDataMapper defaultResponseDataMapper = (pdu, requestInfo) -> { - return pduService.processPdu(pdu, requestInfo.getResponseMappings()); + ResponseDataMapper defaultResponseDataMapper = (pdus, requestContext) -> { + return pduService.processPdus(pdus, requestContext.getResponseMappings()); }; Arrays.stream(SnmpCommunicationSpec.values()) .forEach(communicationSpec -> { @@ -279,34 +384,39 @@ public class SnmpTransportService implements TbTransportService { } private void configureResponseProcessors() { - responseProcessors.put(SnmpCommunicationSpec.TELEMETRY_QUERYING, (responseData, requestInfo, sessionContext) -> { + responseProcessors.put(SnmpCommunicationSpec.TELEMETRY_QUERYING, (responseData, requestContext, sessionContext) -> { TransportProtos.PostTelemetryMsg postTelemetryMsg = JsonConverter.convertToTelemetryProto(responseData); transportService.process(sessionContext.getSessionInfo(), postTelemetryMsg, null); log.debug("Posted telemetry for SNMP device {}: {}", sessionContext.getDeviceId(), responseData); }); - responseProcessors.put(SnmpCommunicationSpec.CLIENT_ATTRIBUTES_QUERYING, (responseData, requestInfo, sessionContext) -> { + responseProcessors.put(SnmpCommunicationSpec.CLIENT_ATTRIBUTES_QUERYING, (responseData, requestContext, sessionContext) -> { TransportProtos.PostAttributeMsg postAttributesMsg = JsonConverter.convertToAttributesProto(responseData); transportService.process(sessionContext.getSessionInfo(), postAttributesMsg, null); log.debug("Posted attributes for SNMP device {}: {}", sessionContext.getDeviceId(), responseData); }); - responseProcessors.put(SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST, (responseData, requestInfo, sessionContext) -> { + responseProcessors.put(SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST, (responseData, requestContext, sessionContext) -> { TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder() - .setRequestId(requestInfo.getRequestId()) + .setRequestId(requestContext.getRequestId()) .setPayload(JsonConverter.toJson(responseData)) .build(); transportService.process(sessionContext.getSessionInfo(), rpcResponseMsg, null); log.debug("Posted RPC response {} for device {}", responseData, sessionContext.getDeviceId()); }); + + responseProcessors.put(SnmpCommunicationSpec.TO_SERVER_RPC_REQUEST, (responseData, requestContext, sessionContext) -> { + TransportProtos.ToServerRpcRequestMsg toServerRpcRequestMsg = TransportProtos.ToServerRpcRequestMsg.newBuilder() + .setRequestId(0) + .setMethodName(requestContext.getMethod().name()) + .setParams(JsonConverter.toJson(responseData)) + .build(); + transportService.process(sessionContext.getSessionInfo(), toServerRpcRequestMsg, null); + }); } private void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { - transportService.process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder() - .setAttributeSubscription(true) - .setRpcSubscription(true) - .setLastActivityTime(System.currentTimeMillis()) - .build(), TransportServiceCallback.EMPTY); + transportService.reportActivity(sessionInfo); } @@ -335,29 +445,34 @@ public class SnmpTransportService implements TbTransportService { } @Data - private static class RequestInfo { - private Integer requestId; - private SnmpCommunicationSpec communicationSpec; - private List responseMappings; + private static class RequestContext { + private final Integer requestId; + private final SnmpCommunicationSpec communicationSpec; + private final SnmpMethod method; + private final List responseMappings; - public RequestInfo(Integer requestId, SnmpCommunicationSpec communicationSpec, List responseMappings) { - this.requestId = requestId; - this.communicationSpec = communicationSpec; - this.responseMappings = responseMappings; - } + private final int requestSize; + private List responseParts; - public RequestInfo(SnmpCommunicationSpec communicationSpec, List responseMappings) { + @Builder + public RequestContext(Integer requestId, SnmpCommunicationSpec communicationSpec, SnmpMethod method, List responseMappings, int requestSize) { + this.requestId = requestId; this.communicationSpec = communicationSpec; + this.method = method; this.responseMappings = responseMappings; + this.requestSize = requestSize; + if (requestSize > 1) { + this.responseParts = Collections.synchronizedList(new ArrayList<>()); + } } } private interface ResponseDataMapper { - JsonObject map(PDU pdu, RequestInfo requestInfo); + JsonObject map(List pdus, RequestContext requestContext); } private interface ResponseProcessor { - void process(JsonObject responseData, RequestInfo requestInfo, DeviceSessionContext sessionContext); + void process(JsonObject responseData, RequestContext requestContext, DeviceSessionContext sessionContext); } } diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java index 2b7c452ca7..b95a11e8c2 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.snmp.session; +import lombok.Builder; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; @@ -26,9 +27,12 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.rpc.RpcStatus; +import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.common.transport.service.DefaultTransportService; import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; @@ -57,17 +61,22 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S private SnmpDeviceTransportConfiguration deviceTransportConfiguration; @Getter private final Device device; + @Getter + private final TenantId tenantId; private final SnmpTransportContext snmpTransportContext; private final AtomicInteger msgIdSeq = new AtomicInteger(0); @Getter private boolean isActive = true; + @Setter + private Runnable sessionTimeoutHandler; @Getter private final List> queryingTasks = new LinkedList<>(); - public DeviceSessionContext(Device device, DeviceProfile deviceProfile, String token, + @Builder + public DeviceSessionContext(TenantId tenantId, Device device, DeviceProfile deviceProfile, String token, SnmpDeviceProfileTransportConfiguration profileTransportConfiguration, SnmpDeviceTransportConfiguration deviceTransportConfiguration, SnmpTransportContext snmpTransportContext) throws Exception { @@ -75,6 +84,7 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S super.setDeviceId(device.getId()); super.setDeviceProfile(deviceProfile); this.device = device; + this.tenantId = tenantId; this.token = token; this.snmpTransportContext = snmpTransportContext; @@ -131,19 +141,32 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S @Override public void onAttributeUpdate(UUID sessionId, AttributeUpdateNotificationMsg attributeUpdateNotification) { log.trace("[{}] Received attributes update notification to device", sessionId); - snmpTransportContext.getSnmpTransportService().onAttributeUpdate(this, attributeUpdateNotification); + try { + snmpTransportContext.getSnmpTransportService().onAttributeUpdate(this, attributeUpdateNotification); + } catch (Exception e) { + snmpTransportContext.getTransportService().errorEvent(getTenantId(), getDeviceId(), SnmpCommunicationSpec.SHARED_ATTRIBUTES_SETTING.getLabel(), e); + } } @Override public void onRemoteSessionCloseCommand(UUID sessionId, SessionCloseNotificationProto sessionCloseNotification) { log.trace("[{}] Received the remote command to close the session: {}", sessionId, sessionCloseNotification.getMessage()); + if (sessionCloseNotification.getMessage().equals(DefaultTransportService.SESSION_EXPIRED_MESSAGE)) { + if (sessionTimeoutHandler != null) { + sessionTimeoutHandler.run(); + } + } } @Override public void onToDeviceRpcRequest(UUID sessionId, ToDeviceRpcRequestMsg toDeviceRequest) { log.trace("[{}] Received RPC command to device", sessionId); - snmpTransportContext.getSnmpTransportService().onToDeviceRpcRequest(this, toDeviceRequest); - snmpTransportContext.getTransportService().process(getSessionInfo(), toDeviceRequest, RpcStatus.DELIVERED, TransportServiceCallback.EMPTY); + try { + snmpTransportContext.getSnmpTransportService().onToDeviceRpcRequest(this, toDeviceRequest); + snmpTransportContext.getTransportService().process(getSessionInfo(), toDeviceRequest, RpcStatus.DELIVERED, TransportServiceCallback.EMPTY); + } catch (Exception e) { + snmpTransportContext.getTransportService().errorEvent(getTenantId(), getDeviceId(), SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST.getLabel(), e); + } } @Override diff --git a/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV2.java b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV2.java index 5fd7f3bed0..fa49b17f13 100644 --- a/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV2.java +++ b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV2.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.transport.snmp; -import org.snmp4j.CommandResponderEvent; import org.snmp4j.CommunityTarget; import org.snmp4j.PDU; import org.snmp4j.Snmp; @@ -35,7 +34,6 @@ import org.snmp4j.agent.mo.snmp.SnmpTargetMIB; import org.snmp4j.agent.mo.snmp.StorageType; import org.snmp4j.agent.mo.snmp.VacmMIB; import org.snmp4j.agent.security.MutableVACM; -import org.snmp4j.mp.MPv3; import org.snmp4j.mp.SnmpConstants; import org.snmp4j.security.SecurityLevel; import org.snmp4j.security.SecurityModel; @@ -53,27 +51,11 @@ import org.snmp4j.transport.TransportMappings; import java.io.File; import java.io.IOException; import java.util.Map; -import java.util.function.Consumer; import java.util.stream.Collectors; @SuppressWarnings("deprecation") public class SnmpDeviceSimulatorV2 extends BaseAgent { - public static class RequestProcessor extends CommandProcessor { - private final Consumer processor; - - public RequestProcessor(Consumer processor) { - super(new OctetString(MPv3.createLocalEngineID())); - this.processor = processor; - } - - @Override - public void processPdu(CommandResponderEvent event) { - processor.accept(event); - } - } - - private final Target target; private final Address address; private Snmp snmp; @@ -81,10 +63,7 @@ public class SnmpDeviceSimulatorV2 extends BaseAgent { private final String password; public SnmpDeviceSimulatorV2(int port, String password) throws IOException { - super(new File("conf.agent"), new File("bootCounter.agent"), new RequestProcessor(event -> { - System.out.println("aboba"); - ((Snmp) event.getSource()).cancel(event.getPDU(), event1 -> System.out.println("canceled")); - })); + super(new File("conf.agent"), new File("bootCounter.agent"), new CommandProcessor(new OctetString("12312"))); CommunityTarget target = new CommunityTarget(); target.setCommunity(new OctetString(password)); this.address = GenericAddress.parse("udp:0.0.0.0/" + port); diff --git a/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV2.java b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV2.java index bf371058aa..5367ef93cf 100644 --- a/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV2.java +++ b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV2.java @@ -16,34 +16,41 @@ package org.thingsboard.server.transport.snmp; import java.io.IOException; +import java.util.HashMap; import java.util.Map; import java.util.Scanner; public class SnmpTestV2 { - public static void main(String[] args) throws IOException { - SnmpDeviceSimulatorV2 device = new SnmpDeviceSimulatorV2(1610, "public"); - device.start(); - device.setUpMappings(Map.of( - ".1.3.6.1.2.1.1.1.50", "12", - ".1.3.6.1.2.1.2.1.52", "56", - ".1.3.6.1.2.1.3.1.54", "yes", - ".1.3.6.1.2.1.7.1.58", "" - )); + private static final Scanner scanner = new Scanner(System.in); + public static void main(String[] args) throws IOException { + SnmpDeviceSimulatorV2 client = new SnmpDeviceSimulatorV2(1610, "public"); -// while (true) { -// new Scanner(System.in).nextLine(); -// device.sendTrap("127.0.0.1", 1062, Map.of(".1.3.6.1.2.87.1.56", "12")); -// System.out.println("sent"); + client.start(); + Map mappings = new HashMap<>(); +// for (int i = 1; i <= 500; i++) { +// String oid = String.format(".1.3.6.1.2.1.%s.1.52", i); +// mappings.put(oid, "value_" + i); // } + mappings.put("1.3.6.1.2.1.266.1.52", "****"); -// Snmp snmp = new Snmp(device.transportMappings[0]); -// device.snmp.addCommandResponder(event -> { -// System.out.println(event); -// }); + client.setUpMappings(mappings); + inputTraps(client); + + scanner.nextLine(); + } - new Scanner(System.in).nextLine(); + private static void inputTraps(SnmpDeviceSimulatorV2 client) throws IOException { + while (true) { + String data = scanner.nextLine(); + if (!data.isEmpty()) { + client.sendTrap("127.0.0.1", 1620, Map.of( + "1.3.6.1.2.1.266.1.52", data + " (266)", + "1.3.6.1.2.1.267.1.52", data + " (267)" + )); + } + } } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index ceef62368a..ab62b4b0df 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -17,6 +17,9 @@ package org.thingsboard.server.common.transport; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; @@ -144,6 +147,10 @@ public interface TransportService { void reportActivity(SessionInfoProto sessionInfo); + void lifecycleEvent(TenantId tenantId, DeviceId deviceId, ComponentLifecycleEvent eventType, boolean success, Throwable error); + + void errorEvent(TenantId tenantId, DeviceId deviceId, String method, Throwable error); + void deregisterSession(SessionInfoProto sessionInfo); void log(SessionInfoProto sessionInfo, String msg); 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 48af9b9195..bc11570fc1 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 @@ -22,6 +22,7 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; import com.google.protobuf.ByteString; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.exception.ExceptionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; @@ -49,6 +50,8 @@ 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.limit.LimitedApi; +import org.thingsboard.server.common.data.notification.rule.trigger.RateLimitsTrigger; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.notification.rule.trigger.RateLimitsTrigger; import org.thingsboard.server.common.data.rpc.RpcStatus; @@ -827,6 +830,37 @@ public class DefaultTransportService implements TransportService { sessionsToRemove.forEach(sessionsActivity::remove); } + @Override + public void lifecycleEvent(TenantId tenantId, DeviceId deviceId, ComponentLifecycleEvent eventType, boolean success, Throwable error) { + ToCoreMsg msg = ToCoreMsg.newBuilder() + .setLifecycleEventMsg(TransportProtos.LifecycleEventProto.newBuilder() + .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) + .setEntityIdMSB(deviceId.getId().getMostSignificantBits()) + .setEntityIdLSB(deviceId.getId().getLeastSignificantBits()) + .setServiceId(serviceInfoProvider.getServiceId()) + .setLcEventType(eventType.name()) + .setSuccess(success) + .setError(error != null ? ExceptionUtils.getStackTrace(error) : "")) + .build(); + sendToCore(tenantId, deviceId, msg, deviceId.getId(), TransportServiceCallback.EMPTY); + } + + @Override + public void errorEvent(TenantId tenantId, DeviceId deviceId, String method, Throwable error) { + ToCoreMsg msg = ToCoreMsg.newBuilder() + .setErrorEventMsg(TransportProtos.ErrorEventProto.newBuilder() + .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) + .setEntityIdMSB(deviceId.getId().getMostSignificantBits()) + .setEntityIdLSB(deviceId.getId().getLeastSignificantBits()) + .setServiceId(serviceInfoProvider.getServiceId()) + .setMethod(method) + .setError(ExceptionUtils.getStackTrace(error))) + .build(); + sendToCore(tenantId, deviceId, msg, deviceId.getId(), TransportServiceCallback.EMPTY); + } + @Override public SessionMetaData registerSyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout) { SessionMetaData currentSession = new SessionMetaData(sessionInfo, TransportProtos.SessionType.SYNC, listener); @@ -1118,18 +1152,21 @@ public class DefaultTransportService implements TransportService { } protected void sendToDeviceActor(TransportProtos.SessionInfoProto sessionInfo, TransportToDeviceActorMsg toDeviceActorMsg, TransportServiceCallback callback) { - TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, getTenantId(sessionInfo), getDeviceId(sessionInfo)); + ToCoreMsg toCoreMsg = ToCoreMsg.newBuilder().setToDeviceActorMsg(toDeviceActorMsg).build(); + sendToCore(getTenantId(sessionInfo), getDeviceId(sessionInfo), toCoreMsg, getRoutingKey(sessionInfo), callback); + } + + private void sendToCore(TenantId tenantId, EntityId entityId, ToCoreMsg msg, UUID routingKey, TransportServiceCallback callback) { + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId); if (log.isTraceEnabled()) { - log.trace("[{}][{}] Pushing to topic {} message {}", getTenantId(sessionInfo), getDeviceId(sessionInfo), tpi.getFullTopicName(), toDeviceActorMsg); + log.trace("[{}][{}] Pushing to topic {} message {}", tenantId, entityId, tpi.getFullTopicName(), msg); } + TransportTbQueueCallback transportTbQueueCallback = callback != null ? new TransportTbQueueCallback(callback) : null; tbCoreProducerStats.incrementTotal(); StatsCallback wrappedCallback = new StatsCallback(transportTbQueueCallback, tbCoreProducerStats); - tbCoreMsgProducer.send(tpi, - new TbProtoQueueMsg<>(getRoutingKey(sessionInfo), - ToCoreMsg.newBuilder().setToDeviceActorMsg(toDeviceActorMsg).build()), - wrappedCallback); + tbCoreMsgProducer.send(tpi, new TbProtoQueueMsg<>(routingKey, msg), wrappedCallback); } private void sendToRuleEngine(TenantId tenantId, TbMsg tbMsg, TbQueueCallback callback) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java index b30ae40808..a4e8a5f82b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java @@ -67,6 +67,8 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; public static final String INCORRECT_DEVICE_ID = "Incorrect deviceId "; public static final String DEFAULT_DEVICE_TELEMETRY_TOPIC = "v1/devices/me/telemetry"; + public static final String HTTP_DEFAULT_PORT = "80"; + public static final String HTTPS_DEFAULT_PORT = "443"; private final Map certs = new ConcurrentHashMap<>(); @@ -203,8 +205,9 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService return null; } String hostName = getHost(baseUrl, properties); - String port = properties.getPort().isEmpty() ? "" : ":" + properties.getPort(); - + String propertiesPort = properties.getPort(); + String port = (propertiesPort.isEmpty() || HTTP_DEFAULT_PORT.equals(propertiesPort) || HTTPS_DEFAULT_PORT.equals(propertiesPort)) + ? "" : ":" + propertiesPort; return DeviceConnectivityUtil.getHttpPublishCommand(protocol, hostName, port, deviceCredentials); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java index 08e047904f..43878adc93 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java @@ -31,7 +31,6 @@ import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.util.ReflectionUtils; import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.rule.RuleChainDao; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.ConstraintValidator; import org.thingsboard.server.dao.service.DataValidator; @@ -41,15 +40,14 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; @Component @Slf4j public class RuleChainDataValidator extends DataValidator { - @Autowired - private RuleChainDao ruleChainDao; - @Autowired @Lazy private RuleChainService ruleChainService; @@ -88,15 +86,19 @@ public class RuleChainDataValidator extends DataValidator { } } - public static void validateMetaData(RuleChainMetaData ruleChainMetaData) { + public static List validateMetaData(RuleChainMetaData ruleChainMetaData) { ConstraintValidator.validateFields(ruleChainMetaData); - ruleChainMetaData.getNodes().forEach(RuleChainDataValidator::validateRuleNode); + List throwables = ruleChainMetaData.getNodes().stream() + .map(RuleChainDataValidator::validateRuleNode) + .filter(Objects::nonNull) + .collect(Collectors.toList()); if (CollectionUtils.isNotEmpty(ruleChainMetaData.getConnections())) { validateCircles(ruleChainMetaData.getConnections()); } + return throwables; } - public static void validateRuleNode(RuleNode ruleNode) { + public static Throwable validateRuleNode(RuleNode ruleNode) { String errorPrefix = "'" + ruleNode.getName() + "' node configuration is invalid: "; ConstraintValidator.validateFields(ruleNode, errorPrefix); Object nodeConfig; @@ -104,11 +106,12 @@ public class RuleChainDataValidator extends DataValidator { Class nodeConfigType = ReflectionUtils.getAnnotationProperty(ruleNode.getType(), "org.thingsboard.rule.engine.api.RuleNode", "configClazz"); nodeConfig = JacksonUtil.treeToValue(ruleNode.getConfiguration(), nodeConfigType); - } catch (Exception e) { - log.warn("Failed to validate node configuration: {}", ExceptionUtils.getRootCauseMessage(e)); - return; + } catch (Throwable t) { + log.warn("Failed to validate node configuration: {}", ExceptionUtils.getRootCauseMessage(t)); + return t; } ConstraintValidator.validateFields(nodeConfig, errorPrefix); + return null; } private static void validateCircles(List connectionInfos) { diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidatorTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidatorTest.java index 7f9d3e1fc2..0fdd1a04d2 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidatorTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidatorTest.java @@ -23,7 +23,6 @@ import org.springframework.boot.test.mock.mockito.SpyBean; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainType; -import org.thingsboard.server.dao.rule.RuleChainDao; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TenantService; @@ -35,8 +34,6 @@ import static org.mockito.Mockito.verify; @SpringBootTest(classes = RuleChainDataValidator.class) class RuleChainDataValidatorTest { - @MockBean - RuleChainDao ruleChainDao; @MockBean RuleChainService ruleChainService; @MockBean diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index b4320577c7..78ca337e0a 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -235,6 +235,8 @@ services: tb-snmp-transport: restart: always image: "${DOCKER_REPO}/${SNMP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" + ports: + - "1620:1620/udp" environment: TB_SERVICE_ID: tb-snmp-transport JAVA_OPTS: "${JAVA_OPTS}" diff --git a/docker/tb-snmp-transport.env b/docker/tb-snmp-transport.env index e2cc39d658..32160de100 100644 --- a/docker/tb-snmp-transport.env +++ b/docker/tb-snmp-transport.env @@ -1,6 +1,7 @@ ZOOKEEPER_ENABLED=true ZOOKEEPER_URL=zookeeper:2181 +SNMP_BIND_PORT=1620 METRICS_ENABLED=true METRICS_ENDPOINTS_EXPOSE=prometheus WEB_APPLICATION_ENABLE=true diff --git a/transport/snmp/src/main/resources/tb-snmp-transport.yml b/transport/snmp/src/main/resources/tb-snmp-transport.yml index 84fdf5ddbb..e141f80321 100644 --- a/transport/snmp/src/main/resources/tb-snmp-transport.yml +++ b/transport/snmp/src/main/resources/tb-snmp-transport.yml @@ -99,11 +99,15 @@ redis: transport: snmp: enabled: "${SNMP_ENABLED:true}" + bind_port: "${SNMP_BIND_PORT:1620}" response_processing: # parallelism level for executor (workStealingPool) that is responsible for handling responses from SNMP devices parallelism_level: "${SNMP_RESPONSE_PROCESSING_PARALLELISM_LEVEL:20}" # to configure SNMP to work over UDP or TCP underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}" + max_request_oids: "${SNMP_MAX_REQUEST_OIDS:100}" + response: + ignore_type_cast_errors: "${SNMP_RESPONSE_IGNORE_TYPE_CAST_ERRORS:false}" sessions: inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}" diff --git a/ui-ngx/src/app/core/translate/missing-translate-handler.ts b/ui-ngx/src/app/core/translate/missing-translate-handler.ts index 229efd8227..4d89e1453d 100644 --- a/ui-ngx/src/app/core/translate/missing-translate-handler.ts +++ b/ui-ngx/src/app/core/translate/missing-translate-handler.ts @@ -21,7 +21,18 @@ export class TbMissingTranslationHandler implements MissingTranslationHandler { handle(params: MissingTranslationHandlerParams) { if (params.key && !params.key.startsWith(customTranslationsPrefix)) { console.warn('Translation for \'' + params.key + '\' doesn\'t exist'); - params.translateService.set(params.key, params.key); + let translations: any; + const parts = params.key.split('.'); + for (let i=parts.length-1; i>=0; i--) { + const newTranslations = {}; + if (i === parts.length-1) { + newTranslations[parts[i]] = params.key; + } else { + newTranslations[parts[i]] = translations; + } + translations = newTranslations; + } + params.translateService.setTranslation(params.translateService.currentLang, translations, true); } } } diff --git a/ui-ngx/src/app/modules/home/components/import-export/import-export.models.ts b/ui-ngx/src/app/modules/home/components/import-export/import-export.models.ts index 4b41f00994..91d4cd0f05 100644 --- a/ui-ngx/src/app/modules/home/components/import-export/import-export.models.ts +++ b/ui-ngx/src/app/modules/home/components/import-export/import-export.models.ts @@ -65,6 +65,10 @@ export enum ImportEntityColumnType { lwm2mServerSecurityMode = 'LWM2M_SERVER_SECURITY_MODE', lwm2mServerClientPublicKeyOrId = 'LWM2M_SERVER_CLIENT_PUBLIC_KEY_OR_ID', lwm2mServerClientSecretKey = 'LWM2M_SERVER_CLIENT_SECRET_KEY', + snmpHost = 'SNMP_HOST', + snmpPort = 'SNMP_PORT', + snmpVersion = 'SNMP_VERSION', + snmpCommunityString = 'SNMP_COMMUNITY_STRING', isGateway = 'IS_GATEWAY', description = 'DESCRIPTION', routingKey = 'ROUTING_KEY', @@ -96,6 +100,10 @@ export const importEntityColumnTypeTranslations = new Map {{ getGroupTitle(source.datasource) }} -
-
-
- - {{key.label}} - - - {{key.settings.icon}} - - icon - - - - {{ getErrorMessageText(key.settings, 'required') }} - - -
-
- - {{key.label}} - - - {{key.settings.icon}} - - icon - - - - {{ getErrorMessageText(key.settings,'required') }} - - - {{ getErrorMessageText(key.settings,'min') }} - - - {{ getErrorMessageText(key.settings,'max') }} - - -
-
- - {{key.label}} - -
-
- - {{key.label}} - - - {{key.settings.icon}} - - icon - - - - - {{ getErrorMessageText(key.settings,'required') }} - - - {{ getErrorMessageText(key.settings,'invalidJSON') | translate }} - - -
-
- - - {{key.settings.icon}} - - icon - - - {{key.label}} - -
-
- - {{key.label}} - - - - - {{ getErrorMessageText(key.settings, 'required') }} - - - {{ getErrorMessageText(key.settings, 'invalidDate') }} - - -
-
- - {{key.label}} - - - {{ getCustomTranslationText(option.label ? option.label : option.value) }} - - - - {{ getErrorMessageText(key.settings, 'required') }} - - -
- -
-
- - {{key.settings.icon}} - - icon - - - {{key.label}} -
- +
+ + + {{key.label}} + + + + + + {{ getErrorMessageText(key.settings, 'required') }} + + + + {{key.label}} + + + + + + {{ getErrorMessageText(key.settings,'required') }} + + + {{ getErrorMessageText(key.settings,'min') }} + + + {{ getErrorMessageText(key.settings,'max') }} + + + + {{key.label}} + + + + + + + {{ getErrorMessageText(key.settings,'required') }} + + + {{ getErrorMessageText(key.settings,'invalidJSON') | translate }} + + + + {{key.label}} + + + + + {{ getErrorMessageText(key.settings, 'required') }} + + + {{ getErrorMessageText(key.settings, 'invalidDate') }} + + + + {{key.label}} + + + {{ getCustomTranslationText(option.label ? option.label : option.value) }} + + + + + + + {{ getErrorMessageText(key.settings, 'required') }} + + + + {{key.label}} + + + + + -
-
+ + {{ getErrorMessageText(key.settings, 'required') }} + + + + + + + {{key.label}} + + + + + + {{key.label}} + +
@@ -216,3 +206,9 @@ + + {{key.settings.icon}} + + icon + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss index 846becbc6f..69e4905f94 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss @@ -38,33 +38,7 @@ } .tb-multiple-input-layout { - display: flex; - flex-direction: row; - align-items: start; - } - - .color-picker-input { - padding: 7px 16px 7px 12px; - margin: 0 10px 22px 0; - border-color: rgba(0, 0, 0, 0.4); - - .label-container { - display: flex; - flex-direction: row; - align-items: center; - } - - .mat-icon, img { - margin-right: 5px; - } - } - - .input-field { - padding-right: 10px; - - mat-form-field { - margin-bottom: 5px; - } + display: grid; } .mat-mdc-slide-toggle { @@ -73,17 +47,6 @@ margin-bottom: 8px; } - .date-time-input { - .mat-mdc-form-field { - width: 100%; - margin: 2px 0; - } - } - - .vertical-alignment { - flex-direction: column; - } - &--buttons-container { display: flex; flex-direction: row; @@ -112,18 +75,33 @@ :host ::ng-deep { .tb-multiple-input { - .mat-mdc-slide-toggle .mdc-form-field { - width: 100%; - & > label { - display: flex; - align-items: center; + .mat-mdc-slide-toggle, .mat-mdc-checkbox { + .mdc-form-field { width: 100%; - margin: 0; - .mat-icon { - margin-left: 8px; - margin-right: 8px; + & > label { + display: flex; + align-items: center; + width: 100%; + margin: 0; + .mat-icon { + margin-right: 8px; + } } } } + .mat-mdc-slide-toggle { + .mdc-form-field { + & > label { + .mat-icon { + margin-left: 8px; + } + } + } + } + .color-input { + .mat-mdc-form-field-icon-suffix { + padding: 0 8px 0 4px; + } + } } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts index f667c785c1..d4c0ad2630 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts @@ -48,6 +48,7 @@ import { } from '@shared/components/dialog/json-object-edit-dialog.component'; import { MatDialog } from '@angular/material/dialog'; import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; +import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field'; type FieldAlignment = 'row' | 'column'; @@ -55,7 +56,7 @@ type MultipleInputWidgetDataKeyType = 'server' | 'shared' | 'timeseries'; export type MultipleInputWidgetDataKeyValueType = 'string' | 'double' | 'integer' | 'JSON' | 'booleanCheckbox' | 'booleanSwitch' | 'dateTime' | 'date' | 'time' | 'select' | 'color'; -type MultipleInputWidgetDataKeyEditableType = 'editable' | 'disabled' | 'readonly'; +export type MultipleInputWidgetDataKeyEditableType = 'editable' | 'disabled' | 'readonly'; type ConvertGetValueFunction = (value: any, ctx: WidgetContext) => any; type ConvertSetValueFunction = (value: any, originValue: any, ctx: WidgetContext) => any; @@ -71,6 +72,8 @@ interface MultipleInputWidgetSettings { groupTitle: string; fieldsAlignment: FieldAlignment; fieldsInRow: number; + columnGap: number; + rowGap: number; attributesShared?: boolean; } @@ -88,6 +91,8 @@ interface MultipleInputWidgetDataKeySettings { isEditable: MultipleInputWidgetDataKeyEditableType; disabledOnDataKey: string; dataKeyHidden: boolean; + appearance: MatFormFieldAppearance; + subscriptSizing: SubscriptSizing; step?: number; minValue?: number; maxValue?: number; @@ -148,8 +153,7 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni private isSavingInProgress = false; isVerticalAlignment: boolean; - inputWidthSettings: string; - changeAlignment: boolean; + columns: number; saveButtonLabel: string; resetButtonLabel: string; @@ -228,15 +232,15 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni if (isUndefined(this.settings.fieldsInRow)) { this.settings.fieldsInRow = 2; } - // For backward compatibility - this.isVerticalAlignment = !(this.settings.fieldsAlignment === 'row'); - - if (!this.isVerticalAlignment && this.settings.fieldsInRow) { - this.inputWidthSettings = 100 / this.settings.fieldsInRow + '%'; + if (isUndefined(this.settings.columnGap)) { + this.settings.columnGap = 10; + } + if (isUndefined(this.settings.rowGap)) { + this.settings.rowGap = 5; } - this.updateWidgetDisplaying(); + this.updateColumns(); } private updateDatasources() { @@ -284,6 +288,15 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni dataKey.settings.isEditable = 'editable'; } } + + if (isUndefined(dataKey.settings.appearance)) { + dataKey.settings.appearance = 'outline'; + } + + if (isUndefined(dataKey.settings.subscriptSizing)) { + dataKey.settings.subscriptSizing = 'fixed'; + } + // For backward compatibility if (dataKey.settings.dataKeyValueType === 'select') { @@ -387,12 +400,6 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni } }); } - } else if (key.settings.dataKeyValueType === 'color') { - formControl.valueChanges.pipe( - takeUntil(this.destroy$) - ).subscribe(() => { - this.inputChanged(source, key); - }); } this.multipleInputFormGroup.addControl(key.formId, formControl); } @@ -452,9 +459,8 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni } if (key.settings.isEditable === 'editable' && key.settings.disabledOnDataKey) { - const conditions = data.filter((item) => { - return source.datasource === item.datasource && item.dataKey.name === key.settings.disabledOnDataKey; - }); + const conditions = data.filter((item) => + source.datasource === item.datasource && item.dataKey.name === key.settings.disabledOnDataKey); if (conditions && conditions.length) { if (conditions[0].data.length) { if (conditions[0].data[0][1] === 'false') { @@ -494,8 +500,17 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni return data; } - private updateWidgetDisplaying() { - this.changeAlignment = (this.ctx.$container && this.ctx.$container[0].offsetWidth < 620); + private updateColumns() { + const changeAlignment = (this.ctx.$container && this.ctx.$container[0].offsetWidth < 620); + if (changeAlignment) { + this.columns = 1; + } else { + if (!this.isVerticalAlignment && this.settings.fieldsInRow) { + this.columns = this.settings.fieldsInRow; + } else { + this.columns = 1; + } + } } public onDataUpdated() { @@ -504,7 +519,7 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni } private resize() { - this.updateWidgetDisplaying(); + this.updateColumns(); this.ctx.detectChanges(); } @@ -599,6 +614,13 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni } } + public colorChanged(source: MultipleInputWidgetSource, key: MultipleInputWidgetDataKey, color: string) { + this.multipleInputFormGroup.get(key.formId).setValue(color); + this.multipleInputFormGroup.get(key.formId).markAsDirty(); + this.multipleInputFormGroup.get(key.formId).markAsTouched(); + this.inputChanged(source, key); + } + public saveForm() { if (this.settings.showActionButtons) { this.save(); @@ -737,7 +759,7 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni this.multipleInputFormGroup.reset(undefined, {emitEvent: false}); this.sources.forEach((source) => { for (const key of this.visibleKeys(source)) { - this.multipleInputFormGroup.get(key.formId).patchValue(key.value, {emitEvent: false}); + this.multipleInputFormGroup.get(key.formId).patchValue(key.value); } }); this.multipleInputFormGroup.markAsPristine(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html index d69a4b0713..22571baab4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html @@ -93,11 +93,36 @@ - + widgets.input-widgets.disable-on-datakey-name +
+ + widgets.input-widgets.field-appearance + + + {{ 'widgets.input-widgets.appearance-fill' | translate }} + + + {{ 'widgets.input-widgets.appearance-outline' | translate }} + + + + + widgets.input-widgets.subscript-sizing + + + {{ 'widgets.input-widgets.subscript-sizing-fixed' | translate }} + + + {{ 'widgets.input-widgets.subscript-sizing-dynamic' | translate }} + + + +
@@ -208,7 +233,9 @@
-
+
widgets.input-widgets.icon-settings {{ 'widgets.input-widgets.use-custom-icon' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.ts index 7eff1bdc80..785ae0cca6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.ts @@ -24,7 +24,10 @@ import { dataKeySelectOptionValidator } from '@home/components/widget/lib/settings/input/datakey-select-option.component'; import { CdkDragDrop } from '@angular/cdk/drag-drop'; -import { MultipleInputWidgetDataKeyValueType } from '@home/components/widget/lib/multiple-input-widget.component'; +import { + MultipleInputWidgetDataKeyEditableType, + MultipleInputWidgetDataKeyValueType +} from '@home/components/widget/lib/multiple-input-widget.component'; @Component({ selector: 'tb-update-multiple-attributes-key-settings', @@ -52,6 +55,8 @@ export class UpdateMultipleAttributesKeySettingsComponent extends WidgetSettings required: false, isEditable: 'editable', disabledOnDataKey: '', + appearance: 'outline', + subscriptSizing: 'fixed', slideToggleLabelPosition: 'after', selectOptions: [], @@ -92,6 +97,8 @@ export class UpdateMultipleAttributesKeySettingsComponent extends WidgetSettings required: [settings.required, []], isEditable: [settings.isEditable, []], disabledOnDataKey: [settings.disabledOnDataKey, []], + appearance: [settings.appearance, []], + subscriptSizing: [settings.subscriptSizing, []], // Slide toggle settings @@ -146,7 +153,7 @@ export class UpdateMultipleAttributesKeySettingsComponent extends WidgetSettings const dataKeyValueType: MultipleInputWidgetDataKeyValueType = this.updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value; const required: boolean = this.updateMultipleAttributesKeySettingsForm.get('required').value; - const isEditable: string = this.updateMultipleAttributesKeySettingsForm.get('isEditable').value; + const isEditable: MultipleInputWidgetDataKeyEditableType = this.updateMultipleAttributesKeySettingsForm.get('isEditable').value; const useCustomIcon: boolean = this.updateMultipleAttributesKeySettingsForm.get('useCustomIcon').value; const useGetValueFunction: boolean = this.updateMultipleAttributesKeySettingsForm.get('useGetValueFunction').value; const useSetValueFunction: boolean = this.updateMultipleAttributesKeySettingsForm.get('useSetValueFunction').value; @@ -163,10 +170,15 @@ export class UpdateMultipleAttributesKeySettingsComponent extends WidgetSettings this.updateMultipleAttributesKeySettingsForm.get('useGetValueFunction').enable({emitEvent: false}); this.updateMultipleAttributesKeySettingsForm.get('useSetValueFunction').enable({emitEvent: false}); - if (isEditable !== 'disabled') { + if (isEditable === 'editable') { this.updateMultipleAttributesKeySettingsForm.get('disabledOnDataKey').enable({emitEvent: false}); } + if (!['booleanSwitch', 'booleanCheckbox'].includes(dataKeyValueType)) { + this.updateMultipleAttributesKeySettingsForm.get('appearance').enable({emitEvent: false}); + this.updateMultipleAttributesKeySettingsForm.get('subscriptSizing').enable({emitEvent: false}); + } + if (dataKeyValueType === 'booleanSwitch') { this.updateMultipleAttributesKeySettingsForm.get('slideToggleLabelPosition').enable({emitEvent: false}); } else if (dataKeyValueType === 'select') { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-widget-settings.component.html index 9c250e5968..c1536040dd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-widget-settings.component.html @@ -72,7 +72,7 @@
- widgets.input-widgets.fields-alignment + widgets.input-widgets.layout
widgets.input-widgets.fields-alignment @@ -90,5 +90,15 @@
+
+ + widgets.input-widgets.row-gap + + + + widgets.input-widgets.column-gap + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-widget-settings.component.ts index c1cf455f9c..9b55e131a8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-widget-settings.component.ts @@ -49,7 +49,9 @@ export class UpdateMultipleAttributesWidgetSettingsComponent extends WidgetSetti showGroupTitle: false, groupTitle: '', fieldsAlignment: 'row', - fieldsInRow: 2 + fieldsInRow: 2, + rowGap: 5, + columnGap: 10 }; } @@ -77,6 +79,11 @@ export class UpdateMultipleAttributesWidgetSettingsComponent extends WidgetSetti fieldsAlignment: [settings.fieldsAlignment, []], fieldsInRow: [settings.fieldsInRow, [Validators.min(1)]], + + // Layout gap + + rowGap: [settings.rowGap, [Validators.min(0)]], + columnGap: [settings.columnGap, [Validators.min(0)]] }); } @@ -105,13 +112,16 @@ export class UpdateMultipleAttributesWidgetSettingsComponent extends WidgetSetti } if (fieldsAlignment === 'row') { this.updateMultipleAttributesWidgetSettingsForm.get('fieldsInRow').enable(); + this.updateMultipleAttributesWidgetSettingsForm.get('columnGap').enable(); } else { this.updateMultipleAttributesWidgetSettingsForm.get('fieldsInRow').disable(); + this.updateMultipleAttributesWidgetSettingsForm.get('columnGap').disable(); } this.updateMultipleAttributesWidgetSettingsForm.get('updateAllValues').updateValueAndValidity({emitEvent}); this.updateMultipleAttributesWidgetSettingsForm.get('saveButtonLabel').updateValueAndValidity({emitEvent}); this.updateMultipleAttributesWidgetSettingsForm.get('resetButtonLabel').updateValueAndValidity({emitEvent}); this.updateMultipleAttributesWidgetSettingsForm.get('groupTitle').updateValueAndValidity({emitEvent}); this.updateMultipleAttributesWidgetSettingsForm.get('fieldsInRow').updateValueAndValidity({emitEvent}); + this.updateMultipleAttributesWidgetSettingsForm.get('columnGap').updateValueAndValidity({emitEvent}); } } diff --git a/ui-ngx/src/app/shared/components/color-input.component.html b/ui-ngx/src/app/shared/components/color-input.component.html index 68ec5854f2..cd40dde783 100644 --- a/ui-ngx/src/app/shared/components/color-input.component.html +++ b/ui-ngx/src/app/shared/components/color-input.component.html @@ -40,7 +40,7 @@ class="tb-box-button" [disabled]="disabled" #matButton - (click)="openColorPickerPopup($event, matButton)"> + (click)="openColorPickerPopup($event, matButton._elementRef)">
diff --git a/ui-ngx/src/app/shared/components/color-input.component.ts b/ui-ngx/src/app/shared/components/color-input.component.ts index 834fcb8176..6d7c7cf8b9 100644 --- a/ui-ngx/src/app/shared/components/color-input.component.ts +++ b/ui-ngx/src/app/shared/components/color-input.component.ts @@ -14,7 +14,16 @@ /// limitations under the License. /// -import { ChangeDetectorRef, Component, forwardRef, Input, OnInit, Renderer2, ViewContainerRef } from '@angular/core'; +import { + ChangeDetectorRef, + Component, + ElementRef, + forwardRef, + Input, + OnInit, + Renderer2, + ViewContainerRef +} from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -84,6 +93,10 @@ export class ColorInputComponent extends PageComponent implements OnInit, Contro @Input() disabled: boolean; + @Input() + @coerceBoolean() + readonly = false; + private modelValue: string; private propagateChange = null; @@ -151,43 +164,47 @@ export class ColorInputComponent extends PageComponent implements OnInit, Contro showColorPicker($event: MouseEvent) { $event.stopPropagation(); - this.dialogs.colorPicker(this.colorFormGroup.get('color').value, - this.colorClearButton).subscribe( - (result) => { - if (!result?.canceled) { - this.colorFormGroup.patchValue( - {color: result?.color}, {emitEvent: true} - ); - this.cd.markForCheck(); - } - } - ); + if (!this.disabled && !this.readonly) { + this.dialogs.colorPicker(this.colorFormGroup.get('color').value, + this.colorClearButton).subscribe( + (result) => { + if (!result?.canceled) { + this.colorFormGroup.patchValue( + {color: result?.color}, {emitEvent: true} + ); + this.cd.markForCheck(); + } + } + ); + } } - openColorPickerPopup($event: Event, matButton: MatButton) { + openColorPickerPopup($event: Event, element?: ElementRef) { if ($event) { $event.stopPropagation(); } - const trigger = matButton._elementRef.nativeElement; - if (this.popoverService.hasPopover(trigger)) { - this.popoverService.hidePopover(trigger); - } else { - const colorPickerPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, ColorPickerPanelComponent, 'left', true, null, - { - color: this.colorFormGroup.get('color').value, - colorClearButton: this.colorClearButton - }, - {}, - {}, {}, true); - colorPickerPopover.tbComponentRef.instance.popover = colorPickerPopover; - colorPickerPopover.tbComponentRef.instance.colorSelected.subscribe((color) => { - colorPickerPopover.hide(); - this.colorFormGroup.patchValue( - {color}, {emitEvent: true} - ); - this.cd.markForCheck(); - }); + if (!this.disabled && !this.readonly) { + const trigger = element ? element.nativeElement : $event.target; + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } else { + const colorPickerPopover = this.popoverService.displayPopover(trigger, this.renderer, + this.viewContainerRef, ColorPickerPanelComponent, 'left', true, null, + { + color: this.colorFormGroup.get('color').value, + colorClearButton: this.colorClearButton + }, + {}, + {}, {}, true); + colorPickerPopover.tbComponentRef.instance.popover = colorPickerPopover; + colorPickerPopover.tbComponentRef.instance.colorSelected.subscribe((color) => { + colorPickerPopover.hide(); + this.colorFormGroup.patchValue( + {color}, {emitEvent: true} + ); + this.cd.markForCheck(); + }); + } } } diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index 66419a10a8..345dd92b1e 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -298,14 +298,16 @@ export enum SnmpSpecType { TELEMETRY_QUERYING = 'TELEMETRY_QUERYING', CLIENT_ATTRIBUTES_QUERYING = 'CLIENT_ATTRIBUTES_QUERYING', SHARED_ATTRIBUTES_SETTING = 'SHARED_ATTRIBUTES_SETTING', - TO_DEVICE_RPC_REQUEST = 'TO_DEVICE_RPC_REQUEST' + TO_DEVICE_RPC_REQUEST = 'TO_DEVICE_RPC_REQUEST', + TO_SERVER_RPC_REQUEST = 'TO_SERVER_RPC_REQUEST' } export const SnmpSpecTypeTranslationMap = new Map([ - [SnmpSpecType.TELEMETRY_QUERYING, ' Telemetry'], - [SnmpSpecType.CLIENT_ATTRIBUTES_QUERYING, 'Client attributes'], - [SnmpSpecType.SHARED_ATTRIBUTES_SETTING, 'Shared attributes'], - [SnmpSpecType.TO_DEVICE_RPC_REQUEST, 'RPC request'] + [SnmpSpecType.TELEMETRY_QUERYING, ' Telemetry (SNMP GET)'], + [SnmpSpecType.CLIENT_ATTRIBUTES_QUERYING, 'Client attributes (SNMP GET)'], + [SnmpSpecType.SHARED_ATTRIBUTES_SETTING, 'Shared attributes (SNMP SET)'], + [SnmpSpecType.TO_DEVICE_RPC_REQUEST, 'To-device RPC request (SNMP GET/SET)'], + [SnmpSpecType.TO_SERVER_RPC_REQUEST, 'From-device RPC request (SNMP TRAP)'] ]); export interface SnmpCommunicationConfig { diff --git a/ui-ngx/src/assets/dashboard/api_usage.json b/ui-ngx/src/assets/dashboard/api_usage.json index 769849cc9e..5733b011f9 100644 --- a/ui-ngx/src/assets/dashboard/api_usage.json +++ b/ui-ngx/src/assets/dashboard/api_usage.json @@ -7,11 +7,7 @@ "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": { @@ -117,8 +113,27 @@ } ], "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { - "timewindowMs": 60000 + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694083375825, + "endTimeMs": 1694169775825 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 } }, "showTitle": false, @@ -162,14 +177,11 @@ }, "row": 0, "col": 0, - "id": "fd6df872-2ddf-0921-3929-2e7f55062fad" + "id": "fd6df872-2ddf-0921-3929-2e7f55062fad", + "typeFullFqn": "system.cards.html_value_card" }, "7e235874-461b-e7c2-2fdd-d8762a020773": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "html_value_card", "type": "latest", - "title": "New widget", "sizeX": 7.5, "sizeY": 3, "config": { @@ -275,8 +287,27 @@ } ], "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { - "timewindowMs": 60000 + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694083375826, + "endTimeMs": 1694169775826 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 } }, "showTitle": false, @@ -320,14 +351,11 @@ }, "row": 0, "col": 0, - "id": "7e235874-461b-e7c2-2fdd-d8762a020773" + "id": "7e235874-461b-e7c2-2fdd-d8762a020773", + "typeFullFqn": "system.cards.html_value_card" }, "08545554-a0e8-05c7-66df-6000cfeff8a4": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "html_value_card", "type": "latest", - "title": "New widget", "sizeX": 7.5, "sizeY": 3, "config": { @@ -433,8 +461,27 @@ } ], "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { - "timewindowMs": 60000 + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694083375826, + "endTimeMs": 1694169775826 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 } }, "showTitle": false, @@ -488,14 +535,11 @@ }, "row": 0, "col": 0, - "id": "08545554-a0e8-05c7-66df-6000cfeff8a4" + "id": "08545554-a0e8-05c7-66df-6000cfeff8a4", + "typeFullFqn": "system.cards.html_value_card" }, "a245c67e-53ec-d299-fa89-69fe2062ccb2": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "html_value_card", "type": "latest", - "title": "New widget", "sizeX": 7.5, "sizeY": 3, "config": { @@ -614,8 +658,27 @@ } ], "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { - "timewindowMs": 60000 + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694083375826, + "endTimeMs": 1694169775826 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 } }, "showTitle": false, @@ -659,14 +722,11 @@ }, "row": 0, "col": 0, - "id": "a245c67e-53ec-d299-fa89-69fe2062ccb2" + "id": "a245c67e-53ec-d299-fa89-69fe2062ccb2", + "typeFullFqn": "system.cards.html_value_card" }, "a151ae60-0326-6116-d818-9070dda8e9c7": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "html_value_card", "type": "latest", - "title": "New widget", "sizeX": 7.5, "sizeY": 3, "config": { @@ -772,8 +832,27 @@ } ], "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { - "timewindowMs": 60000 + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694083375826, + "endTimeMs": 1694169775826 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 } }, "showTitle": false, @@ -822,14 +901,11 @@ }, "row": 0, "col": 0, - "id": "a151ae60-0326-6116-d818-9070dda8e9c7" + "id": "a151ae60-0326-6116-d818-9070dda8e9c7", + "typeFullFqn": "system.cards.html_value_card" }, "68e16e98-0420-f72c-4848-41dedffd3904": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "timeseries_bars_flot", "type": "timeseries", - "title": "New widget", "sizeX": 8, "sizeY": 5, "config": { @@ -926,6 +1002,16 @@ "xaxisSecond": { "axisPosition": "top", "showLabels": true + }, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true } }, "title": "{i18n:api-usage.rule-engine-hourly-activity}", @@ -937,7 +1023,6 @@ }, "widgetStyle": {}, "useDashboardTimewindow": false, - "showLegend": true, "actions": { "headerButton": [ { @@ -966,27 +1051,15 @@ "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 - } + "titleTooltip": "" }, "row": 0, "col": 0, - "id": "68e16e98-0420-f72c-4848-41dedffd3904" + "id": "68e16e98-0420-f72c-4848-41dedffd3904", + "typeFullFqn": "system.charts.timeseries_bars_flot" }, "2aa6b499-6e27-b315-6833-89c4d58485ce": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "timeseries_bars_flot", "type": "timeseries", - "title": "New widget", "sizeX": 8, "sizeY": 5, "config": { @@ -1119,7 +1192,17 @@ "axisPosition": "top", "showLabels": true }, - "tooltipCumulative": false + "tooltipCumulative": false, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } }, "title": "{i18n:api-usage.transport-hourly-activity}", "dropShadow": true, @@ -1130,7 +1213,6 @@ }, "widgetStyle": {}, "useDashboardTimewindow": false, - "showLegend": true, "actions": { "headerButton": [ { @@ -1149,27 +1231,15 @@ "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 - } + "titleTooltip": "" }, "row": 0, "col": 0, - "id": "2aa6b499-6e27-b315-6833-89c4d58485ce" + "id": "2aa6b499-6e27-b315-6833-89c4d58485ce", + "typeFullFqn": "system.charts.timeseries_bars_flot" }, "d890cea3-fba0-6474-9a21-fa780230dc62": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "timeseries_bars_flot", "type": "timeseries", - "title": "New widget", "sizeX": 8, "sizeY": 5, "config": { @@ -1266,6 +1336,16 @@ "xaxisSecond": { "axisPosition": "top", "showLabels": true + }, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true } }, "title": "{i18n:api-usage.javascript-functions-hourly-activity}", @@ -1277,7 +1357,6 @@ }, "widgetStyle": {}, "useDashboardTimewindow": false, - "showLegend": true, "actions": { "headerButton": [ { @@ -1296,27 +1375,15 @@ "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 - } + "titleTooltip": "" }, "row": 0, "col": 0, - "id": "d890cea3-fba0-6474-9a21-fa780230dc62" + "id": "d890cea3-fba0-6474-9a21-fa780230dc62", + "typeFullFqn": "system.charts.timeseries_bars_flot" }, "84b6cfa5-1449-e0f2-3560-f810d2dd7ead": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "timeseries_bars_flot", "type": "timeseries", - "title": "New widget", "sizeX": 8, "sizeY": 5, "config": { @@ -1413,6 +1480,16 @@ "xaxisSecond": { "axisPosition": "top", "showLabels": true + }, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true } }, "title": "{i18n:api-usage.telemetry-persistence-hourly-activity}", @@ -1424,7 +1501,6 @@ }, "widgetStyle": {}, "useDashboardTimewindow": false, - "showLegend": true, "actions": { "headerButton": [ { @@ -1443,27 +1519,15 @@ "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 - } + "titleTooltip": "" }, "row": 0, "col": 0, - "id": "84b6cfa5-1449-e0f2-3560-f810d2dd7ead" + "id": "84b6cfa5-1449-e0f2-3560-f810d2dd7ead", + "typeFullFqn": "system.charts.timeseries_bars_flot" }, "d296b566-a000-7402-ae9d-c815381c5435": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "timeseries_bars_flot", "type": "timeseries", - "title": "New widget", "sizeX": 8, "sizeY": 5, "config": { @@ -1560,6 +1624,16 @@ "xaxisSecond": { "axisPosition": "top", "showLabels": true + }, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true } }, "title": "{i18n:api-usage.alarms-created-hourly-activity}", @@ -1571,7 +1645,6 @@ }, "widgetStyle": {}, "useDashboardTimewindow": false, - "showLegend": true, "actions": { "headerButton": [ { @@ -1595,27 +1668,15 @@ "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 - } + "titleTooltip": "" }, "row": 0, "col": 0, - "id": "d296b566-a000-7402-ae9d-c815381c5435" + "id": "d296b566-a000-7402-ae9d-c815381c5435", + "typeFullFqn": "system.charts.timeseries_bars_flot" }, "00a02464-9509-911b-3b5e-21fb37629822": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "timeseries_bars_flot", "type": "timeseries", - "title": "New widget", "sizeX": 8, "sizeY": 5, "config": { @@ -1747,6 +1808,16 @@ "xaxisSecond": { "axisPosition": "top", "showLabels": true + }, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true } }, "title": "{i18n:api-usage.notifications-hourly-activity}", @@ -1758,7 +1829,6 @@ }, "widgetStyle": {}, "useDashboardTimewindow": false, - "showLegend": true, "actions": { "headerButton": [ { @@ -1782,27 +1852,15 @@ "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 - } + "titleTooltip": "" }, "row": 0, "col": 0, - "id": "00a02464-9509-911b-3b5e-21fb37629822" + "id": "00a02464-9509-911b-3b5e-21fb37629822", + "typeFullFqn": "system.charts.timeseries_bars_flot" }, "74199074-7873-6c6a-2a51-3fc614769f03": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "timeseries_bars_flot", "type": "timeseries", - "title": "New widget", "sizeX": 8, "sizeY": 5, "config": { @@ -1900,6 +1958,16 @@ "xaxisSecond": { "axisPosition": "top", "showLabels": true + }, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true } }, "title": "{i18n:api-usage.rule-engine-daily-activity}", @@ -1911,33 +1979,20 @@ }, "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 - } + "titleTooltip": "" }, "row": 0, "col": 0, - "id": "74199074-7873-6c6a-2a51-3fc614769f03" + "id": "74199074-7873-6c6a-2a51-3fc614769f03", + "typeFullFqn": "system.charts.timeseries_bars_flot" }, "00006bc3-8a8d-b55e-ed39-e318f1bcd090": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "timeseries_bars_flot", "type": "timeseries", - "title": "New widget", "sizeX": 8, "sizeY": 5, "config": { @@ -2035,6 +2090,16 @@ "xaxisSecond": { "axisPosition": "top", "showLabels": true + }, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true } }, "title": "{i18n:api-usage.rule-engine-monthly-activity}", @@ -2046,33 +2111,20 @@ }, "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 - } + "titleTooltip": "" }, "row": 0, "col": 0, - "id": "00006bc3-8a8d-b55e-ed39-e318f1bcd090" + "id": "00006bc3-8a8d-b55e-ed39-e318f1bcd090", + "typeFullFqn": "system.charts.timeseries_bars_flot" }, "5f5ca59c-e507-5301-5910-7ad8cd34df40": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "timeseries_bars_flot", "type": "timeseries", - "title": "New widget", "sizeX": 8, "sizeY": 5, "config": { @@ -2170,6 +2222,16 @@ "xaxisSecond": { "axisPosition": "top", "showLabels": true + }, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true } }, "title": "{i18n:api-usage.javascript-functions-daily-activity}", @@ -2181,33 +2243,20 @@ }, "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 - } + "titleTooltip": "" }, "row": 0, "col": 0, - "id": "5f5ca59c-e507-5301-5910-7ad8cd34df40" + "id": "5f5ca59c-e507-5301-5910-7ad8cd34df40", + "typeFullFqn": "system.charts.timeseries_bars_flot" }, "ada32ee9-44ed-48d1-c368-fd0c94b7607f": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "timeseries_bars_flot", "type": "timeseries", - "title": "New widget", "sizeX": 8, "sizeY": 5, "config": { @@ -2305,6 +2354,16 @@ "xaxisSecond": { "axisPosition": "top", "showLabels": true + }, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true } }, "title": "{i18n:api-usage.javascript-functions-monthly-activity}", @@ -2316,33 +2375,20 @@ }, "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 - } + "titleTooltip": "" }, "row": 0, "col": 0, - "id": "ada32ee9-44ed-48d1-c368-fd0c94b7607f" + "id": "ada32ee9-44ed-48d1-c368-fd0c94b7607f", + "typeFullFqn": "system.charts.timeseries_bars_flot" }, "98bae68b-0f35-72f2-a428-9b06889f1554": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "timeseries_bars_flot", "type": "timeseries", - "title": "New widget", "sizeX": 8, "sizeY": 5, "config": { @@ -2476,7 +2522,17 @@ "axisPosition": "top", "showLabels": true }, - "tooltipCumulative": false + "tooltipCumulative": false, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } }, "title": "{i18n:api-usage.transport-daily-activity}", "dropShadow": true, @@ -2487,33 +2543,20 @@ }, "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 - } + "titleTooltip": "" }, "row": 0, "col": 0, - "id": "98bae68b-0f35-72f2-a428-9b06889f1554" + "id": "98bae68b-0f35-72f2-a428-9b06889f1554", + "typeFullFqn": "system.charts.timeseries_bars_flot" }, "e61e5477-5a09-cc25-966b-f613d81da833": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "timeseries_bars_flot", "type": "timeseries", - "title": "New widget", "sizeX": 8, "sizeY": 5, "config": { @@ -2647,7 +2690,17 @@ "axisPosition": "top", "showLabels": true }, - "tooltipCumulative": false + "tooltipCumulative": false, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true + } }, "title": "{i18n:api-usage.transport-monthly-activity}", "dropShadow": true, @@ -2658,33 +2711,20 @@ }, "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 - } + "titleTooltip": "" }, "row": 0, "col": 0, - "id": "e61e5477-5a09-cc25-966b-f613d81da833" + "id": "e61e5477-5a09-cc25-966b-f613d81da833", + "typeFullFqn": "system.charts.timeseries_bars_flot" }, "85fe0738-5326-f069-ab3f-30594bde5fed": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "timeseries_bars_flot", "type": "timeseries", - "title": "New widget", "sizeX": 8, "sizeY": 5, "config": { @@ -2782,6 +2822,16 @@ "xaxisSecond": { "axisPosition": "top", "showLabels": true + }, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true } }, "title": "{i18n:api-usage.telemetry-persistence-daily-activity}", @@ -2793,33 +2843,20 @@ }, "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 - } + "titleTooltip": "" }, "row": 0, "col": 0, - "id": "85fe0738-5326-f069-ab3f-30594bde5fed" + "id": "85fe0738-5326-f069-ab3f-30594bde5fed", + "typeFullFqn": "system.charts.timeseries_bars_flot" }, "eaeb381a-437e-f6e9-60c9-6bc8826fdd44": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "timeseries_bars_flot", "type": "timeseries", - "title": "New widget", "sizeX": 8, "sizeY": 5, "config": { @@ -2917,6 +2954,16 @@ "xaxisSecond": { "axisPosition": "top", "showLabels": true + }, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true } }, "title": "{i18n:api-usage.telemetry-persistence-monthly-activity}", @@ -2928,33 +2975,20 @@ }, "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 - } + "titleTooltip": "" }, "row": 0, "col": 0, - "id": "eaeb381a-437e-f6e9-60c9-6bc8826fdd44" + "id": "eaeb381a-437e-f6e9-60c9-6bc8826fdd44", + "typeFullFqn": "system.charts.timeseries_bars_flot" }, "4b798823-b97d-9d6a-59dc-fcafd897fc23": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "timeseries_bars_flot", "type": "timeseries", - "title": "New widget", "sizeX": 8, "sizeY": 5, "config": { @@ -3052,6 +3086,16 @@ "xaxisSecond": { "axisPosition": "top", "showLabels": true + }, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true } }, "title": "{i18n:api-usage.alarms-created-daily-activity}", @@ -3063,33 +3107,20 @@ }, "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 - } + "titleTooltip": "" }, "row": 0, "col": 0, - "id": "4b798823-b97d-9d6a-59dc-fcafd897fc23" + "id": "4b798823-b97d-9d6a-59dc-fcafd897fc23", + "typeFullFqn": "system.charts.timeseries_bars_flot" }, "6a981580-7490-19dd-f937-b64cbf67a982": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "timeseries_bars_flot", "type": "timeseries", - "title": "New widget", "sizeX": 8, "sizeY": 5, "config": { @@ -3187,6 +3218,16 @@ "xaxisSecond": { "axisPosition": "top", "showLabels": true + }, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true } }, "title": "{i18n:api-usage.alarms-created-monthly-activity}", @@ -3198,33 +3239,20 @@ }, "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 - } + "titleTooltip": "" }, "row": 0, "col": 0, - "id": "6a981580-7490-19dd-f937-b64cbf67a982" + "id": "6a981580-7490-19dd-f937-b64cbf67a982", + "typeFullFqn": "system.charts.timeseries_bars_flot" }, "7302df65-1b0c-579e-bbdb-145126ae3392": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "timeseries_bars_flot", "type": "timeseries", - "title": "New widget", "sizeX": 8, "sizeY": 5, "config": { @@ -3322,6 +3350,16 @@ "xaxisSecond": { "axisPosition": "top", "showLabels": true + }, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true } }, "title": "{i18n:api-usage.sms-messages-daily-activity}", @@ -3333,33 +3371,20 @@ }, "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 - } + "titleTooltip": "" }, "row": 0, "col": 0, - "id": "7302df65-1b0c-579e-bbdb-145126ae3392" + "id": "7302df65-1b0c-579e-bbdb-145126ae3392", + "typeFullFqn": "system.charts.timeseries_bars_flot" }, "fdb385e7-14fe-fc9f-ebdc-b400f26fc66b": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "timeseries_bars_flot", "type": "timeseries", - "title": "New widget", "sizeX": 8, "sizeY": 5, "config": { @@ -3457,6 +3482,16 @@ "xaxisSecond": { "axisPosition": "top", "showLabels": true + }, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true } }, "title": "{i18n:api-usage.sms-messages-monthly-activity}", @@ -3468,33 +3503,20 @@ }, "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 - } + "titleTooltip": "" }, "row": 0, "col": 0, - "id": "fdb385e7-14fe-fc9f-ebdc-b400f26fc66b" + "id": "fdb385e7-14fe-fc9f-ebdc-b400f26fc66b", + "typeFullFqn": "system.charts.timeseries_bars_flot" }, "2408ad30-163e-8221-08e1-a82b638be564": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "basic_timeseries", "type": "timeseries", - "title": "New widget", "sizeX": 12, "sizeY": 7, "config": { @@ -3642,6 +3664,15 @@ "xaxisSecond": { "axisPosition": "top", "showLabels": true + }, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "showMin": true, + "showMax": true, + "showAvg": false, + "showTotal": true } }, "title": "Queue Stats", @@ -3660,25 +3691,13 @@ "widgetStyle": {}, "useDashboardTimewindow": false, "displayTimewindow": true, - "showLegend": true, - "actions": {}, - "legendConfig": { - "direction": "column", - "position": "bottom", - "showMin": true, - "showMax": true, - "showAvg": false, - "showTotal": true - } + "actions": {} }, - "id": "2408ad30-163e-8221-08e1-a82b638be564" + "id": "2408ad30-163e-8221-08e1-a82b638be564", + "typeFullFqn": "system.charts.basic_timeseries" }, "e43dcfe1-b970-6a11-ce0e-5769f3eb5e88": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "basic_timeseries", "type": "timeseries", - "title": "New widget", "sizeX": 12, "sizeY": 7, "config": { @@ -3796,6 +3815,15 @@ "xaxisSecond": { "axisPosition": "top", "showLabels": true + }, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "showMin": true, + "showMax": true, + "showAvg": false, + "showTotal": true } }, "title": "Processing Failures and Timeouts", @@ -3814,25 +3842,13 @@ "widgetStyle": {}, "useDashboardTimewindow": false, "displayTimewindow": true, - "showLegend": true, - "actions": {}, - "legendConfig": { - "direction": "column", - "position": "bottom", - "showMin": true, - "showMax": true, - "showAvg": false, - "showTotal": true - } + "actions": {} }, - "id": "e43dcfe1-b970-6a11-ce0e-5769f3eb5e88" + "id": "e43dcfe1-b970-6a11-ce0e-5769f3eb5e88", + "typeFullFqn": "system.charts.basic_timeseries" }, "a669cf86-e715-efa4-dd9a-b839abf499e9": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "timeseries_table", "type": "timeseries", - "title": "New widget", "sizeX": 24, "sizeY": 5, "config": { @@ -3921,14 +3937,11 @@ "titleTooltip": "", "displayTimewindow": true }, - "id": "a669cf86-e715-efa4-dd9a-b839abf499e9" + "id": "a669cf86-e715-efa4-dd9a-b839abf499e9", + "typeFullFqn": "system.cards.timeseries_table" }, "292eaded-4775-36f7-c896-98d57bdda936": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "html_value_card", "type": "latest", - "title": "New widget", "sizeX": 7.5, "sizeY": 3, "config": { @@ -4047,8 +4060,27 @@ } ], "timewindow": { + "displayValue": "", + "selectedTab": 0, "realtime": { - "timewindowMs": 60000 + "realtimeType": 1, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1694083375827, + "endTimeMs": 1694169775827 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 } }, "showTitle": false, @@ -4097,14 +4129,11 @@ }, "row": 0, "col": 0, - "id": "292eaded-4775-36f7-c896-98d57bdda936" + "id": "292eaded-4775-36f7-c896-98d57bdda936", + "typeFullFqn": "system.cards.html_value_card" }, "b3571a36-2106-1122-7d58-0d38bbb0e9c8": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "timeseries_bars_flot", "type": "timeseries", - "title": "New widget", "sizeX": 8, "sizeY": 5, "config": { @@ -4202,6 +4231,16 @@ "xaxisSecond": { "axisPosition": "top", "showLabels": true + }, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true } }, "title": "{i18n:api-usage.email-messages-daily-activity}", @@ -4213,33 +4252,20 @@ }, "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 - } + "titleTooltip": "" }, "row": 0, "col": 0, - "id": "b3571a36-2106-1122-7d58-0d38bbb0e9c8" + "id": "b3571a36-2106-1122-7d58-0d38bbb0e9c8", + "typeFullFqn": "system.charts.timeseries_bars_flot" }, "aa875b7f-e7c8-7529-1ae7-f456211b59cc": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "timeseries_bars_flot", "type": "timeseries", - "title": "New widget", "sizeX": 8, "sizeY": 5, "config": { @@ -4337,6 +4363,16 @@ "xaxisSecond": { "axisPosition": "top", "showLabels": true + }, + "showLegend": true, + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": true } }, "title": "{i18n:api-usage.email-messages-monthly-activity}", @@ -4348,26 +4384,17 @@ }, "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 - } + "titleTooltip": "" }, "row": 0, "col": 0, - "id": "aa875b7f-e7c8-7529-1ae7-f456211b59cc" + "id": "aa875b7f-e7c8-7529-1ae7-f456211b59cc", + "typeFullFqn": "system.charts.timeseries_bars_flot" } }, "states": { @@ -4465,7 +4492,8 @@ "autoFillHeight": true, "backgroundImageUrl": null, "mobileAutoFillHeight": false, - "mobileRowHeight": 70 + "mobileRowHeight": 70, + "outerMargin": true } } } @@ -4498,7 +4526,8 @@ "autoFillHeight": true, "backgroundImageUrl": null, "mobileAutoFillHeight": false, - "mobileRowHeight": 70 + "mobileRowHeight": 70, + "outerMargin": true } } } @@ -4531,7 +4560,8 @@ "autoFillHeight": true, "backgroundImageUrl": null, "mobileAutoFillHeight": false, - "mobileRowHeight": 70 + "mobileRowHeight": 70, + "outerMargin": true } } } @@ -4564,7 +4594,8 @@ "autoFillHeight": true, "backgroundImageUrl": null, "mobileAutoFillHeight": false, - "mobileRowHeight": 70 + "mobileRowHeight": 70, + "outerMargin": true } } } @@ -4597,7 +4628,8 @@ "autoFillHeight": true, "backgroundImageUrl": null, "mobileAutoFillHeight": false, - "mobileRowHeight": 70 + "mobileRowHeight": 70, + "outerMargin": true } } } @@ -4638,7 +4670,8 @@ "autoFillHeight": true, "backgroundImageUrl": null, "mobileAutoFillHeight": false, - "mobileRowHeight": 70 + "mobileRowHeight": 70, + "outerMargin": true } } } @@ -4683,7 +4716,8 @@ "autoFillHeight": true, "backgroundImageUrl": null, "mobileAutoFillHeight": false, - "mobileRowHeight": 70 + "mobileRowHeight": 70, + "outerMargin": true } } } @@ -4716,7 +4750,8 @@ "autoFillHeight": true, "backgroundImageUrl": null, "mobileAutoFillHeight": false, - "mobileRowHeight": 70 + "mobileRowHeight": 70, + "outerMargin": true } } } @@ -4737,8 +4772,10 @@ "filter": { "type": "assetType", "resolveMultiple": true, - "assetType": "TbServiceQueue", - "assetNameFilter": "" + "assetNameFilter": "", + "assetTypes": [ + "TbServiceQueue" + ] } } }, @@ -4770,5 +4807,6 @@ "showDashboardLogo": false } }, + "externalId": null, "name": "Api Usage" -} +} \ No newline at end of file diff --git a/ui-ngx/src/assets/dashboard/customer_user_home_page.json b/ui-ngx/src/assets/dashboard/customer_user_home_page.json index 8aee6c108e..7c79162d5b 100644 --- a/ui-ngx/src/assets/dashboard/customer_user_home_page.json +++ b/ui-ngx/src/assets/dashboard/customer_user_home_page.json @@ -7,13 +7,7 @@ "description": "", "widgets": { "867f82cf-ecf2-2d5c-35cb-08c6f2edc3a4": { - "isSystemType": true, - "bundleAlias": "home_page_widgets", - "typeAlias": "documentation_links", "type": "static", - "title": "New widget", - "image": null, - "description": null, "sizeX": 7.5, "sizeY": 3, "config": { @@ -41,16 +35,11 @@ }, "row": 0, "col": 0, - "id": "867f82cf-ecf2-2d5c-35cb-08c6f2edc3a4" + "id": "867f82cf-ecf2-2d5c-35cb-08c6f2edc3a4", + "typeFullFqn": "system.home_page_widgets.documentation_links" }, "9e3ef045-d8bc-1640-a3f4-2dd10b19d50e": { - "isSystemType": true, - "bundleAlias": "home_page_widgets", - "typeAlias": "quick_links", "type": "static", - "title": "New widget", - "image": null, - "description": null, "sizeX": 7.5, "sizeY": 3, "config": { @@ -73,16 +62,11 @@ }, "row": 0, "col": 0, - "id": "9e3ef045-d8bc-1640-a3f4-2dd10b19d50e" + "id": "9e3ef045-d8bc-1640-a3f4-2dd10b19d50e", + "typeFullFqn": "system.home_page_widgets.quick_links" }, "6d6e2b1d-6ce7-4678-3745-c6b0897b2674": { - "isSystemType": true, - "bundleAlias": "home_page_widgets", - "typeAlias": "dashboards", "type": "static", - "title": "New widget", - "image": null, - "description": null, "sizeX": 7.5, "sizeY": 6.5, "config": { @@ -103,16 +87,11 @@ }, "row": 0, "col": 0, - "id": "6d6e2b1d-6ce7-4678-3745-c6b0897b2674" + "id": "6d6e2b1d-6ce7-4678-3745-c6b0897b2674", + "typeFullFqn": "system.home_page_widgets.dashboards" }, "35d3ac3b-f1af-dc3d-a1d1-11f351d16fc6": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "markdown_card", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 5, "sizeY": 3.5, "config": { @@ -238,16 +217,11 @@ }, "row": 0, "col": 0, - "id": "35d3ac3b-f1af-dc3d-a1d1-11f351d16fc6" + "id": "35d3ac3b-f1af-dc3d-a1d1-11f351d16fc6", + "typeFullFqn": "system.cards.markdown_card" }, "7ac20b6a-dc40-b18e-9f5f-bca20bc693bb": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "markdown_card", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 5, "sizeY": 3.5, "config": { @@ -392,7 +366,8 @@ }, "row": 0, "col": 0, - "id": "7ac20b6a-dc40-b18e-9f5f-bca20bc693bb" + "id": "7ac20b6a-dc40-b18e-9f5f-bca20bc693bb", + "typeFullFqn": "system.cards.markdown_card" } }, "states": { diff --git a/ui-ngx/src/assets/dashboard/sys_admin_home_page.json b/ui-ngx/src/assets/dashboard/sys_admin_home_page.json index b1dfe94f86..f93de01596 100644 --- a/ui-ngx/src/assets/dashboard/sys_admin_home_page.json +++ b/ui-ngx/src/assets/dashboard/sys_admin_home_page.json @@ -7,13 +7,7 @@ "description": "", "widgets": { "d70cc256-4c7b-ee06-9905-b8c5e546605f": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "markdown_card", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 5, "sizeY": 3.5, "config": { @@ -72,16 +66,11 @@ }, "row": 0, "col": 0, - "id": "d70cc256-4c7b-ee06-9905-b8c5e546605f" + "id": "d70cc256-4c7b-ee06-9905-b8c5e546605f", + "typeFullFqn": "system.cards.markdown_card" }, "8ee72d43-678c-4e25-e9a8-7d4cfd7a5f8e": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "timeseries_bars_flot", "type": "timeseries", - "title": "New widget", - "image": null, - "description": null, "sizeX": 8, "sizeY": 5, "config": { @@ -179,7 +168,18 @@ "showLabels": true }, "customLegendEnabled": false, - "dataKeysListForLabels": [] + "dataKeysListForLabels": [], + "showLegend": false, + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": true, + "showTotal": false, + "showLatest": false + } }, "title": "Transport messages", "dropShadow": false, @@ -192,37 +192,21 @@ "padding": "0" }, "useDashboardTimewindow": false, - "showLegend": false, "actions": {}, "displayTimewindow": true, "showTitleIcon": false, "titleTooltip": "", "widgetCss": ".tb-widget-container > .tb-widget {\n border: none !important;\n border-radius: 0 !important;\n box-shadow: none !important;\n}\n\n.tb-widget-container > .tb-widget .flot-base {\n opacity: 0.48;\n}\n", "pageSize": 1024, - "noDataDisplayMessage": "", - "legendConfig": { - "direction": "column", - "position": "bottom", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": true, - "showTotal": false, - "showLatest": false - } + "noDataDisplayMessage": "" }, "row": 0, "col": 0, - "id": "8ee72d43-678c-4e25-e9a8-7d4cfd7a5f8e" + "id": "8ee72d43-678c-4e25-e9a8-7d4cfd7a5f8e", + "typeFullFqn": "system.charts.timeseries_bars_flot" }, "4b5e47ed-c197-a937-d727-041ba8decec2": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "markdown_card", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 5, "sizeY": 3.5, "config": { @@ -307,16 +291,11 @@ }, "row": 0, "col": 0, - "id": "4b5e47ed-c197-a937-d727-041ba8decec2" + "id": "4b5e47ed-c197-a937-d727-041ba8decec2", + "typeFullFqn": "system.cards.markdown_card" }, "eace9148-b02a-48fe-1a95-acd9928aa8c5": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "basic_timeseries", "type": "timeseries", - "title": "New widget", - "image": null, - "description": null, "sizeX": 8, "sizeY": 5, "config": { @@ -501,7 +480,18 @@ "showLabels": true }, "customLegendEnabled": false, - "dataKeysListForLabels": [] + "dataKeysListForLabels": [], + "showLegend": true, + "legendConfig": { + "direction": "row", + "position": "top", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": false, + "showTotal": false, + "showLatest": false + } }, "title": "System Info Chart", "dropShadow": false, @@ -518,31 +508,15 @@ "pageSize": 1024, "units": "%", "noDataDisplayMessage": "", - "showLegend": true, - "legendConfig": { - "direction": "row", - "position": "top", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": false, - "showTotal": false, - "showLatest": false - }, "displayTimewindow": true }, "row": 0, "col": 0, - "id": "eace9148-b02a-48fe-1a95-acd9928aa8c5" + "id": "eace9148-b02a-48fe-1a95-acd9928aa8c5", + "typeFullFqn": "system.charts.basic_timeseries" }, "8acbf5df-f9fc-114d-216f-86f081aa4779": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "markdown_card", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 5, "sizeY": 3.5, "config": { @@ -601,16 +575,11 @@ }, "row": 0, "col": 0, - "id": "8acbf5df-f9fc-114d-216f-86f081aa4779" + "id": "8acbf5df-f9fc-114d-216f-86f081aa4779", + "typeFullFqn": "system.cards.markdown_card" }, "d2784f6c-0518-fd95-1d28-b21f70bdcb10": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "markdown_card", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 5, "sizeY": 3.5, "config": { @@ -668,16 +637,11 @@ }, "row": 0, "col": 0, - "id": "d2784f6c-0518-fd95-1d28-b21f70bdcb10" + "id": "d2784f6c-0518-fd95-1d28-b21f70bdcb10", + "typeFullFqn": "system.cards.markdown_card" }, "66dcf1a2-9d83-6873-c693-d6a5d989b3f8": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "markdown_card", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 5, "sizeY": 3.5, "config": { @@ -735,16 +699,11 @@ }, "row": 0, "col": 0, - "id": "66dcf1a2-9d83-6873-c693-d6a5d989b3f8" + "id": "66dcf1a2-9d83-6873-c693-d6a5d989b3f8", + "typeFullFqn": "system.cards.markdown_card" }, "867f82cf-ecf2-2d5c-35cb-08c6f2edc3a4": { - "isSystemType": true, - "bundleAlias": "home_page_widgets", - "typeAlias": "documentation_links", "type": "static", - "title": "New widget", - "image": null, - "description": null, "sizeX": 7.5, "sizeY": 3, "config": { @@ -772,16 +731,11 @@ }, "row": 0, "col": 0, - "id": "867f82cf-ecf2-2d5c-35cb-08c6f2edc3a4" + "id": "867f82cf-ecf2-2d5c-35cb-08c6f2edc3a4", + "typeFullFqn": "system.home_page_widgets.documentation_links" }, "a23185ad-dc46-806c-0e50-5b21fb080ace": { - "isSystemType": true, - "bundleAlias": "home_page_widgets", - "typeAlias": "getting_started", "type": "static", - "title": "New widget", - "image": null, - "description": null, "sizeX": 7.5, "sizeY": 6.5, "config": { @@ -809,16 +763,11 @@ }, "row": 0, "col": 0, - "id": "a23185ad-dc46-806c-0e50-5b21fb080ace" + "id": "a23185ad-dc46-806c-0e50-5b21fb080ace", + "typeFullFqn": "system.home_page_widgets.getting_started" }, "b35fa1b0-7f95-0b23-fade-d066653a40e1": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "markdown_card", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 5, "sizeY": 3.5, "config": { @@ -930,16 +879,11 @@ }, "row": 0, "col": 0, - "id": "b35fa1b0-7f95-0b23-fade-d066653a40e1" + "id": "b35fa1b0-7f95-0b23-fade-d066653a40e1", + "typeFullFqn": "system.cards.markdown_card" }, "5eabde4c-095f-defc-dd09-6c0afb9b9f7b": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "markdown_card", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 5, "sizeY": 3.5, "config": { @@ -1063,16 +1007,11 @@ }, "row": 0, "col": 0, - "id": "5eabde4c-095f-defc-dd09-6c0afb9b9f7b" + "id": "5eabde4c-095f-defc-dd09-6c0afb9b9f7b", + "typeFullFqn": "system.cards.markdown_card" }, "451e2588-80b8-cd08-2199-1263ed4dc12c": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "markdown_card", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 5, "sizeY": 3.5, "config": { @@ -1196,16 +1135,11 @@ }, "row": 0, "col": 0, - "id": "451e2588-80b8-cd08-2199-1263ed4dc12c" + "id": "451e2588-80b8-cd08-2199-1263ed4dc12c", + "typeFullFqn": "system.cards.markdown_card" }, "01b94c17-84eb-4e49-6a92-c3f108ecff8e": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "markdown_card", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 5, "sizeY": 3.5, "config": { @@ -1417,16 +1351,11 @@ }, "row": 0, "col": 0, - "id": "01b94c17-84eb-4e49-6a92-c3f108ecff8e" + "id": "01b94c17-84eb-4e49-6a92-c3f108ecff8e", + "typeFullFqn": "system.cards.markdown_card" }, "f0479b00-ed47-0a30-0c36-bde7847aae00": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "markdown_card", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 5, "sizeY": 3.5, "config": { @@ -1488,16 +1417,11 @@ }, "row": 0, "col": 0, - "id": "f0479b00-ed47-0a30-0c36-bde7847aae00" + "id": "f0479b00-ed47-0a30-0c36-bde7847aae00", + "typeFullFqn": "system.cards.markdown_card" }, "57ed0ad9-8fcf-4454-1fa7-d39e22f84570": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "markdown_card", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 5, "sizeY": 3.5, "config": { @@ -1579,16 +1503,11 @@ }, "row": 0, "col": 0, - "id": "57ed0ad9-8fcf-4454-1fa7-d39e22f84570" + "id": "57ed0ad9-8fcf-4454-1fa7-d39e22f84570", + "typeFullFqn": "system.cards.markdown_card" }, "0464b6bc-7863-ba75-4b84-64b8acae2af9": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "markdown_card", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 5, "sizeY": 3.5, "config": { @@ -1670,16 +1589,11 @@ }, "row": 0, "col": 0, - "id": "0464b6bc-7863-ba75-4b84-64b8acae2af9" + "id": "0464b6bc-7863-ba75-4b84-64b8acae2af9", + "typeFullFqn": "system.cards.markdown_card" }, "4c043bba-6ac1-dd23-0796-fe6ae4c3c0a3": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "markdown_card", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 5, "sizeY": 3.5, "config": { @@ -1761,16 +1675,11 @@ }, "row": 0, "col": 0, - "id": "4c043bba-6ac1-dd23-0796-fe6ae4c3c0a3" + "id": "4c043bba-6ac1-dd23-0796-fe6ae4c3c0a3", + "typeFullFqn": "system.cards.markdown_card" }, "8b8d5d4f-2c5f-c768-d4fb-68d6e0b716f4": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "markdown_card", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 5, "sizeY": 3.5, "config": { @@ -1852,16 +1761,11 @@ }, "row": 0, "col": 0, - "id": "8b8d5d4f-2c5f-c768-d4fb-68d6e0b716f4" + "id": "8b8d5d4f-2c5f-c768-d4fb-68d6e0b716f4", + "typeFullFqn": "system.cards.markdown_card" }, "d58bfdd4-18b0-a70c-5d5f-73d12c2ea468": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "markdown_card", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 5, "sizeY": 3.5, "config": { @@ -1943,16 +1847,11 @@ }, "row": 0, "col": 0, - "id": "d58bfdd4-18b0-a70c-5d5f-73d12c2ea468" + "id": "d58bfdd4-18b0-a70c-5d5f-73d12c2ea468", + "typeFullFqn": "system.cards.markdown_card" }, "a3d83738-a4f2-ea92-17d7-1a3c52aa1770": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "markdown_card", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 5, "sizeY": 3.5, "config": { @@ -2034,16 +1933,11 @@ }, "row": 0, "col": 0, - "id": "a3d83738-a4f2-ea92-17d7-1a3c52aa1770" + "id": "a3d83738-a4f2-ea92-17d7-1a3c52aa1770", + "typeFullFqn": "system.cards.markdown_card" }, "ff4f04a2-f739-2f3e-17dc-9ede9f2df75d": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "markdown_card", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 5, "sizeY": 3.5, "config": { @@ -2235,16 +2129,11 @@ }, "row": 0, "col": 0, - "id": "ff4f04a2-f739-2f3e-17dc-9ede9f2df75d" + "id": "ff4f04a2-f739-2f3e-17dc-9ede9f2df75d", + "typeFullFqn": "system.cards.markdown_card" }, "163025d8-3ca4-dd1e-c17b-e40d8e03e8a8": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "markdown_card", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 5, "sizeY": 3.5, "config": { @@ -2306,7 +2195,8 @@ }, "row": 0, "col": 0, - "id": "163025d8-3ca4-dd1e-c17b-e40d8e03e8a8" + "id": "163025d8-3ca4-dd1e-c17b-e40d8e03e8a8", + "typeFullFqn": "system.cards.markdown_card" } }, "states": { @@ -2721,4 +2611,4 @@ }, "externalId": null, "name": "System Administrator Home Page" -} +} \ No newline at end of file diff --git a/ui-ngx/src/assets/dashboard/tenant_admin_home_page.json b/ui-ngx/src/assets/dashboard/tenant_admin_home_page.json index 6f9215b4f9..a2edebb7c4 100644 --- a/ui-ngx/src/assets/dashboard/tenant_admin_home_page.json +++ b/ui-ngx/src/assets/dashboard/tenant_admin_home_page.json @@ -7,13 +7,7 @@ "description": "", "widgets": { "d70cc256-4c7b-ee06-9905-b8c5e546605f": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "markdown_card", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 5, "sizeY": 3.5, "config": { @@ -72,16 +66,11 @@ }, "row": 0, "col": 0, - "id": "d70cc256-4c7b-ee06-9905-b8c5e546605f" + "id": "d70cc256-4c7b-ee06-9905-b8c5e546605f", + "typeFullFqn": "system.cards.markdown_card" }, "8ee72d43-678c-4e25-e9a8-7d4cfd7a5f8e": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "timeseries_bars_flot", "type": "timeseries", - "title": "New widget", - "image": null, - "description": null, "sizeX": 8, "sizeY": 5, "config": { @@ -179,7 +168,18 @@ "showLabels": true }, "customLegendEnabled": false, - "dataKeysListForLabels": [] + "dataKeysListForLabels": [], + "showLegend": false, + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": true, + "showTotal": false, + "showLatest": false + } }, "title": "Transport messages", "dropShadow": false, @@ -192,37 +192,21 @@ "padding": "0" }, "useDashboardTimewindow": false, - "showLegend": false, "actions": {}, "displayTimewindow": true, "showTitleIcon": false, "titleTooltip": "", "widgetCss": ".tb-widget-container > .tb-widget {\n border: none !important;\n border-radius: 0 !important;\n box-shadow: none !important;\n}\n\n.tb-widget-container > .tb-widget .flot-base {\n opacity: 0.48;\n}\n", "pageSize": 1024, - "noDataDisplayMessage": "", - "legendConfig": { - "direction": "column", - "position": "bottom", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": true, - "showTotal": false, - "showLatest": false - } + "noDataDisplayMessage": "" }, "row": 0, "col": 0, - "id": "8ee72d43-678c-4e25-e9a8-7d4cfd7a5f8e" + "id": "8ee72d43-678c-4e25-e9a8-7d4cfd7a5f8e", + "typeFullFqn": "system.charts.timeseries_bars_flot" }, "867f82cf-ecf2-2d5c-35cb-08c6f2edc3a4": { - "isSystemType": true, - "bundleAlias": "home_page_widgets", - "typeAlias": "documentation_links", "type": "static", - "title": "New widget", - "image": null, - "description": null, "sizeX": 7.5, "sizeY": 3, "config": { @@ -250,16 +234,11 @@ }, "row": 0, "col": 0, - "id": "867f82cf-ecf2-2d5c-35cb-08c6f2edc3a4" + "id": "867f82cf-ecf2-2d5c-35cb-08c6f2edc3a4", + "typeFullFqn": "system.home_page_widgets.documentation_links" }, "a23185ad-dc46-806c-0e50-5b21fb080ace": { - "isSystemType": true, - "bundleAlias": "home_page_widgets", - "typeAlias": "getting_started", "type": "static", - "title": "New widget", - "image": null, - "description": null, "sizeX": 7.5, "sizeY": 6.5, "config": { @@ -287,16 +266,11 @@ }, "row": 0, "col": 0, - "id": "a23185ad-dc46-806c-0e50-5b21fb080ace" + "id": "a23185ad-dc46-806c-0e50-5b21fb080ace", + "typeFullFqn": "system.home_page_widgets.getting_started" }, "d26e5cd7-75ef-d475-00c7-1a2d1114efe8": { - "isSystemType": true, - "bundleAlias": "charts", - "typeAlias": "basic_timeseries", "type": "timeseries", - "title": "New widget", - "image": null, - "description": null, "sizeX": 8, "sizeY": 5, "config": { @@ -412,7 +386,18 @@ "showLabels": true }, "customLegendEnabled": false, - "dataKeysListForLabels": [] + "dataKeysListForLabels": [], + "showLegend": false, + "legendConfig": { + "direction": "column", + "position": "bottom", + "sortDataKeys": false, + "showMin": false, + "showMax": false, + "showAvg": true, + "showTotal": false, + "showLatest": false + } }, "title": "Devices activity", "dropShadow": false, @@ -430,31 +415,15 @@ }, "widgetCss": ".tb-widget-container > .tb-widget {\n border: none !important;\n border-radius: 0 !important;\n box-shadow: none !important;\n}\n", "pageSize": 1024, - "noDataDisplayMessage": "", - "showLegend": false, - "legendConfig": { - "direction": "column", - "position": "bottom", - "sortDataKeys": false, - "showMin": false, - "showMax": false, - "showAvg": true, - "showTotal": false, - "showLatest": false - } + "noDataDisplayMessage": "" }, "row": 0, "col": 0, - "id": "d26e5cd7-75ef-d475-00c7-1a2d1114efe8" + "id": "d26e5cd7-75ef-d475-00c7-1a2d1114efe8", + "typeFullFqn": "system.charts.basic_timeseries" }, "ebbd0a6e-8a47-e770-5086-7f4974250f2d": { - "isSystemType": true, - "bundleAlias": "home_page_widgets", - "typeAlias": "usage_info", "type": "static", - "title": "New widget", - "image": null, - "description": null, "sizeX": 7.5, "sizeY": 6.5, "config": { @@ -475,16 +444,11 @@ }, "row": 0, "col": 0, - "id": "ebbd0a6e-8a47-e770-5086-7f4974250f2d" + "id": "ebbd0a6e-8a47-e770-5086-7f4974250f2d", + "typeFullFqn": "system.home_page_widgets.usage_info" }, "9e3ef045-d8bc-1640-a3f4-2dd10b19d50e": { - "isSystemType": true, - "bundleAlias": "home_page_widgets", - "typeAlias": "quick_links", "type": "static", - "title": "New widget", - "image": null, - "description": null, "sizeX": 7.5, "sizeY": 3, "config": { @@ -507,16 +471,11 @@ }, "row": 0, "col": 0, - "id": "9e3ef045-d8bc-1640-a3f4-2dd10b19d50e" + "id": "9e3ef045-d8bc-1640-a3f4-2dd10b19d50e", + "typeFullFqn": "system.home_page_widgets.quick_links" }, "6d6e2b1d-6ce7-4678-3745-c6b0897b2674": { - "isSystemType": true, - "bundleAlias": "home_page_widgets", - "typeAlias": "dashboards", "type": "static", - "title": "New widget", - "image": null, - "description": null, "sizeX": 7.5, "sizeY": 6.5, "config": { @@ -537,16 +496,11 @@ }, "row": 0, "col": 0, - "id": "6d6e2b1d-6ce7-4678-3745-c6b0897b2674" + "id": "6d6e2b1d-6ce7-4678-3745-c6b0897b2674", + "typeFullFqn": "system.home_page_widgets.dashboards" }, "35d3ac3b-f1af-dc3d-a1d1-11f351d16fc6": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "markdown_card", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 5, "sizeY": 3.5, "config": { @@ -672,16 +626,11 @@ }, "row": 0, "col": 0, - "id": "35d3ac3b-f1af-dc3d-a1d1-11f351d16fc6" + "id": "35d3ac3b-f1af-dc3d-a1d1-11f351d16fc6", + "typeFullFqn": "system.cards.markdown_card" }, "7ac20b6a-dc40-b18e-9f5f-bca20bc693bb": { - "isSystemType": true, - "bundleAlias": "cards", - "typeAlias": "markdown_card", "type": "latest", - "title": "New widget", - "image": null, - "description": null, "sizeX": 5, "sizeY": 3.5, "config": { @@ -826,7 +775,8 @@ }, "row": 0, "col": 0, - "id": "7ac20b6a-dc40-b18e-9f5f-bca20bc693bb" + "id": "7ac20b6a-dc40-b18e-9f5f-bca20bc693bb", + "typeFullFqn": "system.cards.markdown_card" } }, "states": { @@ -1101,4 +1051,4 @@ }, "externalId": null, "name": "Tenant Administrator Home Page" -} +} \ No newline at end of file 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 16d80b7897..39c5c7fba0 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2784,6 +2784,12 @@ "lwm2m-server-secret-key": "LwM2M server secret key", "lwm2m-server-public-key-id": "LwM2M server public key or id" }, + "snmp": { + "host": "SNMP host", + "port": "SNMP port", + "version": "SNMP version (v1, v2c or v3)", + "community-string": "SNMP community string" + }, "isgateway": "Is Gateway", "activity-time-from-gateway-device": "Activity time from gateway device", "description": "Description", @@ -5329,6 +5335,9 @@ "input-fields-alignment": "Input fields alignment", "input-fields-alignment-column": "Column (default)", "input-fields-alignment-row": "Row", + "layout": "Layout", + "row-gap": "Gap between rows in pixels", + "column-gap": "Gap between columns in pixels", "latitude-field-required": "Latitude field required", "longitude-field-required": "Longitude field required", "attribute-settings": "Attribute settings", @@ -5384,6 +5393,12 @@ "ability-to-edit-attribute-disabled": "Disabled", "ability-to-edit-attribute-readonly": "Read-only", "disable-on-datakey-name": "Disable on false value of another datakey (specify datakey name)", + "field-appearance": "Field appearance", + "appearance-fill": "Fill", + "appearance-outline": "Outline", + "subscript-sizing": "Subscript sizing", + "subscript-sizing-fixed": "Fixed", + "subscript-sizing-dynamic": "Dynamic", "slide-toggle-settings": "Slide toggle settings", "slide-toggle-label-position": "Slide toggle label position", "slide-toggle-label-position-after": "After",